@bakapiano/ccsm 0.22.2 → 0.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +538 -538
- package/README.md +189 -189
- package/bin/ccsm.js +235 -235
- package/lib/cliActivity.js +139 -139
- package/lib/codexSeed.js +183 -183
- package/lib/config.js +274 -274
- package/lib/devices.js +229 -229
- package/lib/folders.js +124 -124
- package/lib/localCliSessions.js +519 -519
- package/lib/persistedSessions.js +129 -129
- package/lib/tunnel.js +621 -621
- package/lib/webTerminal.js +233 -231
- package/lib/workspace.js +233 -233
- package/package.json +57 -57
- package/public/css/base.css +99 -99
- package/public/css/cards.css +183 -183
- package/public/css/feedback.css +504 -504
- package/public/css/forms.css +453 -453
- package/public/css/layout.css +176 -176
- package/public/css/modal.css +190 -190
- package/public/css/responsive.css +176 -176
- package/public/css/sidebar.css +707 -707
- package/public/css/terminals.css +592 -592
- package/public/css/tokens.css +81 -81
- package/public/css/wco.css +196 -196
- package/public/css/widgets.css +2725 -2725
- package/public/index.html +152 -152
- package/public/js/api.js +371 -371
- package/public/js/backend.js +149 -149
- package/public/js/components/App.js +73 -73
- package/public/js/components/DirectoryPicker.js +203 -203
- package/public/js/components/EntityFormModal.js +153 -153
- package/public/js/components/Modal.js +57 -57
- package/public/js/components/OfflineBanner.js +67 -67
- package/public/js/components/PageTitleBar.js +13 -13
- package/public/js/components/PendingApprovalOverlay.js +128 -128
- package/public/js/components/Picker.js +179 -179
- package/public/js/components/Popover.js +55 -55
- package/public/js/components/RestartOverlay.js +36 -36
- package/public/js/components/Sidebar.js +380 -380
- package/public/js/components/TerminalInstance.js +187 -15
- package/public/js/components/TerminalResizeDebouncer.js +126 -0
- package/public/js/components/XtermTerminal.js +148 -14
- package/public/js/components/useDragSort.js +67 -67
- package/public/js/dialog.js +67 -67
- package/public/js/icons.js +212 -212
- package/public/js/main.js +296 -296
- package/public/js/pages/AboutPage.js +90 -90
- package/public/js/pages/ConfigurePage.js +713 -713
- package/public/js/pages/LaunchPage.js +421 -421
- package/public/js/pages/RemotePage.js +743 -743
- package/public/js/pages/SessionsPage.js +100 -100
- package/public/js/state.js +335 -335
- package/public/manifest.webmanifest +25 -0
- package/public/setup/index.html +567 -0
- package/scripts/dev.js +149 -149
- package/scripts/install.js +153 -153
- package/scripts/restart-helper.js +96 -96
- package/scripts/upgrade-helper.js +687 -687
- package/server.js +1807 -1807
package/public/css/sidebar.css
CHANGED
|
@@ -1,707 +1,707 @@
|
|
|
1
|
-
/* Left collapsible sidebar nav · brand mark · util items · collapse toggle */
|
|
2
|
-
|
|
3
|
-
.sidebar {
|
|
4
|
-
/* One value drives all three "section break" gaps in the sidebar
|
|
5
|
-
column: brand-strip → first nav item, last nav item → "Sessions"
|
|
6
|
-
header, and "Sessions" header → first folder. Bump or shrink to
|
|
7
|
-
adjust how breathy the rail feels. */
|
|
8
|
-
--sidebar-section-gap: var(--s-2);
|
|
9
|
-
position: sticky;
|
|
10
|
-
top: 0;
|
|
11
|
-
height: 100vh;
|
|
12
|
-
background: var(--ui-bg);
|
|
13
|
-
border-right: 1px solid var(--ui-border);
|
|
14
|
-
display: flex;
|
|
15
|
-
flex-direction: column;
|
|
16
|
-
padding: 0 var(--s-2);
|
|
17
|
-
overflow: visible;
|
|
18
|
-
transition: padding .25s cubic-bezier(.4, 0, .2, 1);
|
|
19
|
-
}
|
|
20
|
-
.sidebar[data-collapsed="true"] {
|
|
21
|
-
padding: 0;
|
|
22
|
-
}
|
|
23
|
-
/* Collapsed: every clickable row becomes a 28x28 square, centered in
|
|
24
|
-
the narrow sidebar column. Width auto + margin auto so the square
|
|
25
|
-
sits centered regardless of what padding the parent has. */
|
|
26
|
-
.sidebar[data-collapsed="true"] .nav-item,
|
|
27
|
-
.sidebar[data-collapsed="true"] .util-item,
|
|
28
|
-
.sidebar[data-collapsed="true"] .sidebar-brand-button,
|
|
29
|
-
.sidebar[data-collapsed="true"] .tree-folder-head,
|
|
30
|
-
.sidebar[data-collapsed="true"] .tree-session {
|
|
31
|
-
width: 28px;
|
|
32
|
-
height: 28px;
|
|
33
|
-
min-height: 28px;
|
|
34
|
-
padding: 0;
|
|
35
|
-
margin: 0 auto;
|
|
36
|
-
justify-content: center;
|
|
37
|
-
gap: 0;
|
|
38
|
-
}
|
|
39
|
-
.sidebar[data-collapsed="true"] .sidebar-top {
|
|
40
|
-
padding: 0;
|
|
41
|
-
min-height: 40px;
|
|
42
|
-
justify-content: center;
|
|
43
|
-
}
|
|
44
|
-
.sidebar[data-collapsed="true"] .sidebar-top .collapse-toggle {
|
|
45
|
-
width: 40px;
|
|
46
|
-
height: 40px;
|
|
47
|
-
min-height: 40px;
|
|
48
|
-
margin: 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.sidebar-brand {
|
|
52
|
-
display: flex;
|
|
53
|
-
align-items: center;
|
|
54
|
-
gap: var(--s-2);
|
|
55
|
-
padding: 0 var(--s-2);
|
|
56
|
-
min-height: 28px;
|
|
57
|
-
height: 28px;
|
|
58
|
-
}
|
|
59
|
-
.brand-mark {
|
|
60
|
-
display: inline-flex;
|
|
61
|
-
align-items: center;
|
|
62
|
-
justify-content: center;
|
|
63
|
-
width: 20px;
|
|
64
|
-
height: 20px;
|
|
65
|
-
flex: 0 0 20px;
|
|
66
|
-
background: transparent;
|
|
67
|
-
color: var(--accent);
|
|
68
|
-
}
|
|
69
|
-
.brand-mark svg { display: block; width: 20px; height: 20px; }
|
|
70
|
-
.brand-name {
|
|
71
|
-
font-size: 14px;
|
|
72
|
-
font-weight: 600;
|
|
73
|
-
letter-spacing: -0.02em;
|
|
74
|
-
color: var(--ink);
|
|
75
|
-
white-space: nowrap;
|
|
76
|
-
opacity: 1;
|
|
77
|
-
line-height: 1;
|
|
78
|
-
transition: opacity .15s ease;
|
|
79
|
-
}
|
|
80
|
-
.brand-dot { color: var(--accent); }
|
|
81
|
-
.sidebar[data-collapsed="true"] .brand-name { display: none; }
|
|
82
|
-
.sidebar[data-collapsed="true"] .brand-version { display: none; }
|
|
83
|
-
.sidebar[data-collapsed="true"] .sidebar-brand { justify-content: center; padding-left: 0; padding-right: 0; }
|
|
84
|
-
|
|
85
|
-
.sidebar-nav {
|
|
86
|
-
display: flex;
|
|
87
|
-
flex-direction: column;
|
|
88
|
-
gap: 2px;
|
|
89
|
-
flex: 0 0 auto;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.nav-item, .util-item {
|
|
93
|
-
appearance: none;
|
|
94
|
-
background: transparent;
|
|
95
|
-
border: 0;
|
|
96
|
-
display: flex;
|
|
97
|
-
align-items: center;
|
|
98
|
-
gap: var(--s-3);
|
|
99
|
-
width: 100%;
|
|
100
|
-
padding: 0 12px;
|
|
101
|
-
min-height: 42px;
|
|
102
|
-
border-radius: var(--r-sm);
|
|
103
|
-
cursor: pointer;
|
|
104
|
-
color: var(--ink);
|
|
105
|
-
font-family: var(--body);
|
|
106
|
-
font-size: 13px;
|
|
107
|
-
font-weight: 400;
|
|
108
|
-
text-align: left;
|
|
109
|
-
transition: background .12s ease, color .12s ease;
|
|
110
|
-
position: relative;
|
|
111
|
-
}
|
|
112
|
-
.nav-item:hover, .util-item:hover {
|
|
113
|
-
background: var(--sidebar-hover);
|
|
114
|
-
}
|
|
115
|
-
.nav-item[aria-selected="true"] {
|
|
116
|
-
background: var(--sidebar-active);
|
|
117
|
-
color: var(--ink);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* Unsaved-changes dot next to nav label */
|
|
121
|
-
.nav-item.has-changes::after {
|
|
122
|
-
content: "";
|
|
123
|
-
position: absolute;
|
|
124
|
-
right: 10px;
|
|
125
|
-
top: 50%;
|
|
126
|
-
transform: translateY(-50%);
|
|
127
|
-
width: 7px;
|
|
128
|
-
height: 7px;
|
|
129
|
-
border-radius: 50%;
|
|
130
|
-
background: var(--ink);
|
|
131
|
-
box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.30);
|
|
132
|
-
animation: dirty-pulse 2s ease-in-out infinite;
|
|
133
|
-
}
|
|
134
|
-
@keyframes dirty-pulse {
|
|
135
|
-
0%, 100% { box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.22); }
|
|
136
|
-
50% { box-shadow: 0 0 0 4px rgba(26, 24, 21, 0); }
|
|
137
|
-
}
|
|
138
|
-
.sidebar[data-collapsed="true"] .nav-item.has-changes::after {
|
|
139
|
-
right: auto;
|
|
140
|
-
top: 6px;
|
|
141
|
-
left: 28px;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.nav-icon {
|
|
145
|
-
display: inline-flex;
|
|
146
|
-
width: 20px;
|
|
147
|
-
height: 20px;
|
|
148
|
-
flex: 0 0 20px;
|
|
149
|
-
color: currentColor;
|
|
150
|
-
}
|
|
151
|
-
.nav-icon svg { width: 100%; height: 100%; }
|
|
152
|
-
.nav-label {
|
|
153
|
-
white-space: nowrap;
|
|
154
|
-
opacity: 1;
|
|
155
|
-
transition: opacity .15s ease;
|
|
156
|
-
flex: 1;
|
|
157
|
-
}
|
|
158
|
-
.sidebar[data-collapsed="true"] .nav-label { opacity: 0; pointer-events: none; }
|
|
159
|
-
/* Collapsed sidebar (60px wide): hide the label entirely so the icon
|
|
160
|
-
centers in the narrow column instead of being pushed off-screen by
|
|
161
|
-
the still-laid-out (but invisible) label text. Same for the badge. */
|
|
162
|
-
.sidebar[data-collapsed="true"] .nav-label,
|
|
163
|
-
.sidebar[data-collapsed="true"] .nav-badge {
|
|
164
|
-
display: none;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.nav-badge {
|
|
168
|
-
font-family: var(--mono);
|
|
169
|
-
font-size: 10.5px;
|
|
170
|
-
background: var(--border-soft);
|
|
171
|
-
color: var(--ink-muted);
|
|
172
|
-
padding: 1px 6px;
|
|
173
|
-
border-radius: 4px;
|
|
174
|
-
font-variant-numeric: tabular-nums;
|
|
175
|
-
opacity: 1;
|
|
176
|
-
transition: opacity .15s ease;
|
|
177
|
-
}
|
|
178
|
-
.sidebar[data-collapsed="true"] .nav-badge { opacity: 0; }
|
|
179
|
-
.nav-item[aria-selected="true"] .nav-badge {
|
|
180
|
-
background: var(--bg-elev);
|
|
181
|
-
color: var(--ink-mid);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.sidebar-divider {
|
|
185
|
-
margin: var(--s-3) var(--s-2);
|
|
186
|
-
border-top: 1px solid var(--border);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.sidebar-utility {
|
|
190
|
-
display: flex;
|
|
191
|
-
flex-direction: column;
|
|
192
|
-
gap: 2px;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.sidebar-foot {
|
|
196
|
-
margin-top: auto;
|
|
197
|
-
padding-top: 2px;
|
|
198
|
-
display: flex;
|
|
199
|
-
flex-direction: row;
|
|
200
|
-
justify-content: flex-end;
|
|
201
|
-
align-items: center;
|
|
202
|
-
gap: 2px;
|
|
203
|
-
}
|
|
204
|
-
/* Brand block at the bottom of the sidebar. Clickable: navigates to
|
|
205
|
-
About. Matches nav-item height so collapsed sidebar reads as a
|
|
206
|
-
uniform column of equally-sized icons. */
|
|
207
|
-
.sidebar-brand-button {
|
|
208
|
-
appearance: none;
|
|
209
|
-
background: transparent;
|
|
210
|
-
border: 0;
|
|
211
|
-
display: flex;
|
|
212
|
-
align-items: center;
|
|
213
|
-
gap: 8px;
|
|
214
|
-
width: 100%;
|
|
215
|
-
padding: 0 var(--s-2);
|
|
216
|
-
min-height: 28px;
|
|
217
|
-
height: 28px;
|
|
218
|
-
cursor: pointer;
|
|
219
|
-
border-radius: 4px;
|
|
220
|
-
text-align: left;
|
|
221
|
-
font: inherit;
|
|
222
|
-
color: var(--ink);
|
|
223
|
-
transition: background .12s;
|
|
224
|
-
}
|
|
225
|
-
.sidebar-brand-button[aria-selected="true"],
|
|
226
|
-
.sidebar-brand-button[aria-selected="true"]:hover {
|
|
227
|
-
background: var(--sidebar-active);
|
|
228
|
-
font-weight: 500;
|
|
229
|
-
}
|
|
230
|
-
.sidebar-brand-button:hover { background: var(--sidebar-hover); }
|
|
231
|
-
.sidebar-foot .brand-mark {
|
|
232
|
-
width: 14px;
|
|
233
|
-
height: 14px;
|
|
234
|
-
flex: 0 0 14px;
|
|
235
|
-
display: inline-flex;
|
|
236
|
-
align-items: center;
|
|
237
|
-
justify-content: center;
|
|
238
|
-
/* Lock the SVG box exactly to 14×14 so flex centering doesn't fight
|
|
239
|
-
the SVG's intrinsic dimensions; otherwise the mark drifts a couple
|
|
240
|
-
pixels low because <svg width=32 height=32> wants more vertical
|
|
241
|
-
space than the 14px row gives it. */
|
|
242
|
-
line-height: 0;
|
|
243
|
-
}
|
|
244
|
-
.sidebar-foot .brand-mark svg {
|
|
245
|
-
width: 14px;
|
|
246
|
-
height: 14px;
|
|
247
|
-
display: block;
|
|
248
|
-
}
|
|
249
|
-
.sidebar-foot .brand-name {
|
|
250
|
-
font-size: 13px;
|
|
251
|
-
font-weight: 400;
|
|
252
|
-
line-height: 1;
|
|
253
|
-
}
|
|
254
|
-
.brand-version {
|
|
255
|
-
margin-left: auto;
|
|
256
|
-
font-family: var(--mono);
|
|
257
|
-
font-size: 10px;
|
|
258
|
-
color: var(--ink-muted);
|
|
259
|
-
font-variant-numeric: tabular-nums;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/* Top strip · single row, currently hosts only the collapse toggle on
|
|
263
|
-
the right. Matches the page-title-bar height so the topmost row of
|
|
264
|
-
the window reads as one unified band. */
|
|
265
|
-
.sidebar-top {
|
|
266
|
-
display: flex;
|
|
267
|
-
align-items: center;
|
|
268
|
-
padding: 0;
|
|
269
|
-
min-height: 40px;
|
|
270
|
-
/* Sit flush above the first nav item — no extra breathing room
|
|
271
|
-
between the brand strip and the nav list. */
|
|
272
|
-
margin-bottom: 0;
|
|
273
|
-
}
|
|
274
|
-
.collapse-toggle {
|
|
275
|
-
appearance: none;
|
|
276
|
-
background: transparent;
|
|
277
|
-
border: 0;
|
|
278
|
-
display: inline-flex;
|
|
279
|
-
align-items: center;
|
|
280
|
-
justify-content: center;
|
|
281
|
-
width: 40px;
|
|
282
|
-
height: 40px;
|
|
283
|
-
padding: 0;
|
|
284
|
-
border-radius: 4px;
|
|
285
|
-
cursor: pointer;
|
|
286
|
-
color: var(--ink);
|
|
287
|
-
transition: background .12s;
|
|
288
|
-
min-height: 0;
|
|
289
|
-
flex: 0 0 40px;
|
|
290
|
-
}
|
|
291
|
-
.collapse-toggle:hover { background: var(--sidebar-hover); }
|
|
292
|
-
.collapse-toggle .nav-icon {
|
|
293
|
-
width: 14px;
|
|
294
|
-
height: 14px;
|
|
295
|
-
flex: 0 0 14px;
|
|
296
|
-
}
|
|
297
|
-
.sidebar[data-collapsed="true"] .sidebar-top {
|
|
298
|
-
justify-content: center;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/* chevron flips when the sidebar is collapsed; the rest is util-item styling */
|
|
302
|
-
.collapse-toggle .nav-icon {
|
|
303
|
-
transition: transform .25s cubic-bezier(.4, 0, .2, 1);
|
|
304
|
-
}
|
|
305
|
-
.sidebar[data-collapsed="true"] .collapse-toggle .nav-icon {
|
|
306
|
-
transform: rotate(180deg);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/* Drag-to-resize handle. Sits absolutely against the sidebar's right
|
|
310
|
-
border so the cursor target spans the full height. 6px wide hit area
|
|
311
|
-
centered on the visible 1px border — easy to grab without bumping
|
|
312
|
-
into adjacent layout. */
|
|
313
|
-
.sidebar-resize-handle {
|
|
314
|
-
position: absolute;
|
|
315
|
-
top: 0;
|
|
316
|
-
right: -3px;
|
|
317
|
-
width: 6px;
|
|
318
|
-
height: 100%;
|
|
319
|
-
cursor: col-resize;
|
|
320
|
-
z-index: 5;
|
|
321
|
-
touch-action: none;
|
|
322
|
-
/* Subtle hover indicator: deepens the border-right color on hover so the
|
|
323
|
-
user knows the edge is interactive. */
|
|
324
|
-
background: transparent;
|
|
325
|
-
transition: background .12s ease;
|
|
326
|
-
}
|
|
327
|
-
.sidebar-resize-handle:hover,
|
|
328
|
-
body.is-resizing-sidebar .sidebar-resize-handle {
|
|
329
|
-
/* Wash the hit area in the accent color so the user knows the edge is
|
|
330
|
-
live and so the theme follows their accent choice. */
|
|
331
|
-
background: var(--accent-soft);
|
|
332
|
-
}
|
|
333
|
-
/* While dragging, freeze global cursor + suppress text selection so the
|
|
334
|
-
whole page tracks resize cleanly even if pointer leaves the handle. */
|
|
335
|
-
body.is-resizing-sidebar {
|
|
336
|
-
cursor: col-resize !important;
|
|
337
|
-
user-select: none;
|
|
338
|
-
}
|
|
339
|
-
body.is-resizing-sidebar * {
|
|
340
|
-
cursor: col-resize !important;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/* === v1.0 codex-style sidebar tree === */
|
|
344
|
-
|
|
345
|
-
/* Compact top nav: smaller height + smaller font, so the folder tree
|
|
346
|
-
below dominates. */
|
|
347
|
-
/* Match the dimensions of .tree-folder-head + .tree-session below so
|
|
348
|
-
the top nav, the folder head, and the session rows form one visually
|
|
349
|
-
continuous column: same left padding (icon at x=8), same icon-label
|
|
350
|
-
gap, same row height, same corner radius. Without this the nav
|
|
351
|
-
icons sit 2px further right than the folder icons and the rows are
|
|
352
|
-
noticeably taller. */
|
|
353
|
-
.sidebar-nav.compact .nav-item {
|
|
354
|
-
font-size: 13px;
|
|
355
|
-
padding: 4px 8px;
|
|
356
|
-
min-height: 28px;
|
|
357
|
-
gap: 8px;
|
|
358
|
-
border-radius: 4px;
|
|
359
|
-
position: relative;
|
|
360
|
-
letter-spacing: -0.005em;
|
|
361
|
-
transition: background .14s ease, color .14s ease;
|
|
362
|
-
}
|
|
363
|
-
.sidebar-nav.compact .nav-item:hover { background: var(--sidebar-hover); }
|
|
364
|
-
.sidebar-nav.compact .nav-icon {
|
|
365
|
-
width: 14px;
|
|
366
|
-
height: 14px;
|
|
367
|
-
flex: 0 0 14px;
|
|
368
|
-
}
|
|
369
|
-
.sidebar[data-collapsed="true"] .sidebar-nav.compact .nav-item {
|
|
370
|
-
justify-content: center;
|
|
371
|
-
gap: 0;
|
|
372
|
-
padding: 8px 0;
|
|
373
|
-
}
|
|
374
|
-
.sidebar-nav.compact .nav-item.is-active {
|
|
375
|
-
background: var(--sidebar-active);
|
|
376
|
-
color: var(--ink);
|
|
377
|
-
font-weight: 500;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/* Tree section header. Looks like codex: uppercase label, small +
|
|
381
|
-
button on hover. */
|
|
382
|
-
.tree {
|
|
383
|
-
margin-top: var(--sidebar-section-gap);
|
|
384
|
-
display: flex;
|
|
385
|
-
flex-direction: column;
|
|
386
|
-
gap: 2px;
|
|
387
|
-
/* Only vertical scroll when the tree is taller than the rail.
|
|
388
|
-
overflow-y alone leaves overflow-x at the default `auto`, which
|
|
389
|
-
gives us an unwanted horizontal scrollbar whenever a session
|
|
390
|
-
title (or its hover row padding) brushes the viewport edge.
|
|
391
|
-
overflow-x:hidden truncates with ellipsis instead — labels
|
|
392
|
-
already do `text-overflow: ellipsis`. */
|
|
393
|
-
overflow-y: auto;
|
|
394
|
-
overflow-x: hidden;
|
|
395
|
-
flex: 1;
|
|
396
|
-
min-height: 0;
|
|
397
|
-
padding-bottom: var(--s-3);
|
|
398
|
-
}
|
|
399
|
-
.tree-head {
|
|
400
|
-
display: flex;
|
|
401
|
-
justify-content: space-between;
|
|
402
|
-
align-items: center;
|
|
403
|
-
/* Right padding matches .tree-session so the +-button glyph
|
|
404
|
-
right-aligns with the per-row tree-meta timestamp below. */
|
|
405
|
-
padding: 0 8px;
|
|
406
|
-
min-height: 24px;
|
|
407
|
-
height: 24px;
|
|
408
|
-
font-size: 12px;
|
|
409
|
-
font-weight: 500;
|
|
410
|
-
letter-spacing: 0;
|
|
411
|
-
color: var(--ink-mid);
|
|
412
|
-
/* No margin-bottom — let the parent .tree's `gap: 2px` carry the
|
|
413
|
-
space below, matching what sits between folder rows. The big gap
|
|
414
|
-
above "Sessions" still comes from .tree margin-top. */
|
|
415
|
-
}
|
|
416
|
-
.tree-head-action {
|
|
417
|
-
appearance: none;
|
|
418
|
-
background: transparent;
|
|
419
|
-
border: 0;
|
|
420
|
-
/* Zero right padding so the glyph itself lands flush against the
|
|
421
|
-
row's 8px right inset — same column as the tree-meta text on
|
|
422
|
-
each session row. */
|
|
423
|
-
padding: 2px 0 2px 4px;
|
|
424
|
-
border-radius: 4px;
|
|
425
|
-
cursor: pointer;
|
|
426
|
-
color: var(--ink);
|
|
427
|
-
display: inline-flex;
|
|
428
|
-
align-items: center;
|
|
429
|
-
opacity: 0;
|
|
430
|
-
transition: opacity .12s, background .12s;
|
|
431
|
-
}
|
|
432
|
-
.tree-head:hover .tree-head-action,
|
|
433
|
-
.tree-head-action:focus-visible { opacity: 0.7; }
|
|
434
|
-
.tree-head:hover .tree-head-action:hover { opacity: 1; background: var(--sidebar-hover); }
|
|
435
|
-
.tree-head-action svg { width: 14px; height: 14px; }
|
|
436
|
-
|
|
437
|
-
/* Folder grouping. Chevron rotates on expand. */
|
|
438
|
-
.tree-folder {
|
|
439
|
-
display: flex;
|
|
440
|
-
flex-direction: column;
|
|
441
|
-
}
|
|
442
|
-
.tree-folder[data-dnd-over="true"] > .tree-folder-head {
|
|
443
|
-
box-shadow: 0 -2px 0 var(--accent) inset;
|
|
444
|
-
}
|
|
445
|
-
/* Session being dragged → folder is a drop target. Tint the folder
|
|
446
|
-
head + outline the whole folder so the user knows where it'll land,
|
|
447
|
-
independent of whether the folder is expanded or collapsed. */
|
|
448
|
-
.tree-folder.is-session-drop-target {
|
|
449
|
-
border-radius: 6px;
|
|
450
|
-
outline: 1px dashed var(--ink-mid);
|
|
451
|
-
outline-offset: -2px;
|
|
452
|
-
background: var(--sidebar-hover);
|
|
453
|
-
}
|
|
454
|
-
.tree-session[draggable="true"] { cursor: pointer; }
|
|
455
|
-
.tree-session[draggable="true"]:active { cursor: grabbing; }
|
|
456
|
-
.tree-folder-head[draggable="true"] {
|
|
457
|
-
cursor: grab;
|
|
458
|
-
}
|
|
459
|
-
.tree-folder-head[draggable="true"]:active {
|
|
460
|
-
cursor: grabbing;
|
|
461
|
-
}
|
|
462
|
-
.tree-folder-head {
|
|
463
|
-
appearance: none;
|
|
464
|
-
background: transparent;
|
|
465
|
-
border: 0;
|
|
466
|
-
width: 100%;
|
|
467
|
-
text-align: left;
|
|
468
|
-
padding: 4px 8px;
|
|
469
|
-
display: flex;
|
|
470
|
-
align-items: center;
|
|
471
|
-
gap: 8px;
|
|
472
|
-
font-size: 13px;
|
|
473
|
-
font-weight: 400;
|
|
474
|
-
color: var(--ink);
|
|
475
|
-
cursor: pointer;
|
|
476
|
-
border-radius: 4px;
|
|
477
|
-
font-family: var(--body);
|
|
478
|
-
min-height: 28px;
|
|
479
|
-
}
|
|
480
|
-
.tree-folder-head:hover { background: var(--sidebar-hover); }
|
|
481
|
-
.tree-folder-icon {
|
|
482
|
-
display: inline-flex;
|
|
483
|
-
width: 14px;
|
|
484
|
-
height: 14px;
|
|
485
|
-
flex: 0 0 14px;
|
|
486
|
-
color: var(--ink);
|
|
487
|
-
}
|
|
488
|
-
.tree-folder-icon svg { width: 100%; height: 100%; }
|
|
489
|
-
.tree-folder-head.is-open .tree-folder-icon { color: var(--ink); }
|
|
490
|
-
.tree-folder-name {
|
|
491
|
-
flex: 1;
|
|
492
|
-
white-space: nowrap;
|
|
493
|
-
overflow: hidden;
|
|
494
|
-
text-overflow: ellipsis;
|
|
495
|
-
}
|
|
496
|
-
.tree-folder-count {
|
|
497
|
-
font-size: 13px;
|
|
498
|
-
color: var(--ink);
|
|
499
|
-
font-variant-numeric: tabular-nums;
|
|
500
|
-
padding: 0 5px;
|
|
501
|
-
background: transparent;
|
|
502
|
-
border-radius: 999px;
|
|
503
|
-
opacity: 0.6;
|
|
504
|
-
}
|
|
505
|
-
.tree-folder-actions {
|
|
506
|
-
display: none;
|
|
507
|
-
gap: 2px;
|
|
508
|
-
}
|
|
509
|
-
/* Same touch carve-out as session-actions above — don't reveal folder
|
|
510
|
-
rename/delete on a tap-emulated hover. */
|
|
511
|
-
@media (hover: hover) and (pointer: fine) {
|
|
512
|
-
.tree-folder-head:hover .tree-folder-actions { display: inline-flex; }
|
|
513
|
-
}
|
|
514
|
-
.tree-folder-action {
|
|
515
|
-
appearance: none;
|
|
516
|
-
background: transparent;
|
|
517
|
-
border: 0;
|
|
518
|
-
display: inline-flex;
|
|
519
|
-
align-items: center;
|
|
520
|
-
justify-content: center;
|
|
521
|
-
width: 18px;
|
|
522
|
-
height: 18px;
|
|
523
|
-
padding: 0;
|
|
524
|
-
font-size: 13px;
|
|
525
|
-
color: var(--ink);
|
|
526
|
-
cursor: pointer;
|
|
527
|
-
opacity: 0.6;
|
|
528
|
-
border-radius: 3px;
|
|
529
|
-
}
|
|
530
|
-
.tree-folder-action:hover { opacity: 1; background: var(--sidebar-hover); }
|
|
531
|
-
.tree-folder-action svg { width: 12px; height: 12px; }
|
|
532
|
-
|
|
533
|
-
.tree-folder-body {
|
|
534
|
-
display: flex;
|
|
535
|
-
flex-direction: column;
|
|
536
|
-
/* No left padding here — the session rows themselves get padding-left
|
|
537
|
-
that lines their label up under the folder name. Keeping the bg
|
|
538
|
-
extend across the full sidebar width when selected/hovered. */
|
|
539
|
-
padding-left: 0;
|
|
540
|
-
}
|
|
541
|
-
.tree-empty {
|
|
542
|
-
font-size: 13px;
|
|
543
|
-
color: var(--ink);
|
|
544
|
-
padding: 3px 8px;
|
|
545
|
-
font-style: italic;
|
|
546
|
-
opacity: 0.5;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/* Session rows. Codex uses a colored dot + truncated label + tiny
|
|
550
|
-
timestamp on the right. */
|
|
551
|
-
.tree-session {
|
|
552
|
-
display: flex;
|
|
553
|
-
align-items: center;
|
|
554
|
-
gap: 8px;
|
|
555
|
-
/* Match the folder head: 8px left padding, then a 14px icon column
|
|
556
|
-
(the colored dot lives here, sized 8px and centered), then 8px gap
|
|
557
|
-
before the label — lines the label up exactly under the folder name. */
|
|
558
|
-
padding: 4px 8px;
|
|
559
|
-
border-radius: 4px;
|
|
560
|
-
cursor: pointer;
|
|
561
|
-
font-size: 13px;
|
|
562
|
-
color: var(--ink);
|
|
563
|
-
font-family: var(--body);
|
|
564
|
-
user-select: none;
|
|
565
|
-
transition: background .1s;
|
|
566
|
-
min-height: 28px;
|
|
567
|
-
}
|
|
568
|
-
.tree-session:hover { background: var(--sidebar-hover); }
|
|
569
|
-
.tree-session.is-active {
|
|
570
|
-
background: var(--sidebar-active);
|
|
571
|
-
font-weight: 500;
|
|
572
|
-
}
|
|
573
|
-
/* Drop-line shown above the row when a sibling session is being
|
|
574
|
-
dragged over it for within-folder reorder. Top-only inset shadow
|
|
575
|
-
reads as a 2px insertion mark without shifting layout. */
|
|
576
|
-
.tree-session.is-reorder-target {
|
|
577
|
-
position: relative;
|
|
578
|
-
}
|
|
579
|
-
.tree-session.is-reorder-target::before {
|
|
580
|
-
content: "";
|
|
581
|
-
position: absolute;
|
|
582
|
-
top: -1px;
|
|
583
|
-
left: 4px;
|
|
584
|
-
right: 4px;
|
|
585
|
-
height: 2px;
|
|
586
|
-
background: var(--ink-mid);
|
|
587
|
-
border-radius: 1px;
|
|
588
|
-
pointer-events: none;
|
|
589
|
-
}
|
|
590
|
-
/* Status dot · deliberately understated. The earlier version had a
|
|
591
|
-
green dot + soft glow + expanding halo pulse; in a sidebar with
|
|
592
|
-
eight running sessions it read as a row of strobing alerts. Now:
|
|
593
|
-
one 5px dot, no halo, no shadow, no animation. Color alone carries
|
|
594
|
-
running vs stopped. */
|
|
595
|
-
.tree-dot {
|
|
596
|
-
width: 14px;
|
|
597
|
-
height: 14px;
|
|
598
|
-
flex: 0 0 14px;
|
|
599
|
-
display: inline-flex;
|
|
600
|
-
align-items: center;
|
|
601
|
-
justify-content: center;
|
|
602
|
-
}
|
|
603
|
-
.tree-dot::after {
|
|
604
|
-
content: "";
|
|
605
|
-
width: 7px;
|
|
606
|
-
height: 7px;
|
|
607
|
-
border-radius: 50%;
|
|
608
|
-
background: var(--ink-faint);
|
|
609
|
-
transition: background .15s ease;
|
|
610
|
-
}
|
|
611
|
-
.tree-session.is-running .tree-dot::after {
|
|
612
|
-
background: var(--green);
|
|
613
|
-
/* Soft halo so the dot reads as "alive" even from across the sidebar.
|
|
614
|
-
Box-shadow uses currentColor isn't ideal here (the dot itself uses
|
|
615
|
-
background, not color), so we hardcode each state's halo color
|
|
616
|
-
below. */
|
|
617
|
-
box-shadow: 0 0 0 0 rgba(74, 138, 74, 0.55);
|
|
618
|
-
animation: tree-dot-breathe-idle 2.8s ease-in-out infinite;
|
|
619
|
-
}
|
|
620
|
-
/* Working = CLI is actively writing to its transcript (i.e. thinking
|
|
621
|
-
or printing tokens). Idle stays green + slow breathe; working flips
|
|
622
|
-
to blue + faster, more obvious breathe. */
|
|
623
|
-
.tree-session.is-running.is-working .tree-dot::after {
|
|
624
|
-
background: var(--blue, #4a73a5);
|
|
625
|
-
box-shadow: 0 0 0 0 rgba(74, 115, 165, 0.65);
|
|
626
|
-
animation: tree-dot-breathe-working 1.4s ease-in-out infinite;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
@keyframes tree-dot-breathe-idle {
|
|
630
|
-
0%, 100% {
|
|
631
|
-
box-shadow: 0 0 0 0 rgba(74, 138, 74, 0.45);
|
|
632
|
-
opacity: 0.85;
|
|
633
|
-
}
|
|
634
|
-
50% {
|
|
635
|
-
box-shadow: 0 0 0 4px rgba(74, 138, 74, 0);
|
|
636
|
-
opacity: 1;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
@keyframes tree-dot-breathe-working {
|
|
640
|
-
0%, 100% {
|
|
641
|
-
box-shadow: 0 0 0 0 rgba(74, 115, 165, 0.65);
|
|
642
|
-
opacity: 0.9;
|
|
643
|
-
transform: scale(1);
|
|
644
|
-
}
|
|
645
|
-
50% {
|
|
646
|
-
box-shadow: 0 0 0 5px rgba(74, 115, 165, 0);
|
|
647
|
-
opacity: 1;
|
|
648
|
-
transform: scale(1.15);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
/* Respect users who've asked for less motion — keep the color signal
|
|
652
|
-
but drop the pulse. */
|
|
653
|
-
@media (prefers-reduced-motion: reduce) {
|
|
654
|
-
.tree-session.is-running .tree-dot::after,
|
|
655
|
-
.tree-session.is-running.is-working .tree-dot::after {
|
|
656
|
-
animation: none;
|
|
657
|
-
box-shadow: none;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
.tree-label {
|
|
661
|
-
flex: 1;
|
|
662
|
-
white-space: nowrap;
|
|
663
|
-
overflow: hidden;
|
|
664
|
-
text-overflow: ellipsis;
|
|
665
|
-
}
|
|
666
|
-
.tree-session-actions {
|
|
667
|
-
display: none;
|
|
668
|
-
gap: 2px;
|
|
669
|
-
flex-shrink: 0;
|
|
670
|
-
}
|
|
671
|
-
/* Hover-only on real-pointer devices. On touch devices the first tap
|
|
672
|
-
emulates :hover (revealing rename/delete), which means the user has
|
|
673
|
-
to tap a second time to actually open the session. (hover: hover)
|
|
674
|
-
matches a real mouse / trackpad; (pointer: fine) further requires
|
|
675
|
-
precise cursor (so hybrid touch+mouse laptops with a stylus get
|
|
676
|
-
hover but pure-touch phones / tablets do not.) Phones keep the
|
|
677
|
-
timestamp visible and never reveal the action buttons here — they
|
|
678
|
-
can use the kebab in the session pane's top bar instead. */
|
|
679
|
-
@media (hover: hover) and (pointer: fine) {
|
|
680
|
-
.tree-session:hover .tree-session-actions { display: inline-flex; }
|
|
681
|
-
.tree-session:hover .tree-meta { display: none; }
|
|
682
|
-
}
|
|
683
|
-
.tree-session-action {
|
|
684
|
-
appearance: none;
|
|
685
|
-
background: transparent;
|
|
686
|
-
border: 0;
|
|
687
|
-
display: inline-flex;
|
|
688
|
-
align-items: center;
|
|
689
|
-
justify-content: center;
|
|
690
|
-
width: 18px;
|
|
691
|
-
height: 18px;
|
|
692
|
-
padding: 0;
|
|
693
|
-
color: var(--ink);
|
|
694
|
-
cursor: pointer;
|
|
695
|
-
opacity: 0.6;
|
|
696
|
-
border-radius: 3px;
|
|
697
|
-
}
|
|
698
|
-
.tree-session-action:hover { opacity: 1; background: var(--sidebar-hover); }
|
|
699
|
-
.tree-session-action svg { width: 12px; height: 12px; }
|
|
700
|
-
.tree-meta {
|
|
701
|
-
font-size: 12px;
|
|
702
|
-
color: var(--ink);
|
|
703
|
-
font-variant-numeric: tabular-nums;
|
|
704
|
-
flex-shrink: 0;
|
|
705
|
-
opacity: 0.55;
|
|
706
|
-
letter-spacing: 0.01em;
|
|
707
|
-
}
|
|
1
|
+
/* Left collapsible sidebar nav · brand mark · util items · collapse toggle */
|
|
2
|
+
|
|
3
|
+
.sidebar {
|
|
4
|
+
/* One value drives all three "section break" gaps in the sidebar
|
|
5
|
+
column: brand-strip → first nav item, last nav item → "Sessions"
|
|
6
|
+
header, and "Sessions" header → first folder. Bump or shrink to
|
|
7
|
+
adjust how breathy the rail feels. */
|
|
8
|
+
--sidebar-section-gap: var(--s-2);
|
|
9
|
+
position: sticky;
|
|
10
|
+
top: 0;
|
|
11
|
+
height: 100vh;
|
|
12
|
+
background: var(--ui-bg);
|
|
13
|
+
border-right: 1px solid var(--ui-border);
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
padding: 0 var(--s-2);
|
|
17
|
+
overflow: visible;
|
|
18
|
+
transition: padding .25s cubic-bezier(.4, 0, .2, 1);
|
|
19
|
+
}
|
|
20
|
+
.sidebar[data-collapsed="true"] {
|
|
21
|
+
padding: 0;
|
|
22
|
+
}
|
|
23
|
+
/* Collapsed: every clickable row becomes a 28x28 square, centered in
|
|
24
|
+
the narrow sidebar column. Width auto + margin auto so the square
|
|
25
|
+
sits centered regardless of what padding the parent has. */
|
|
26
|
+
.sidebar[data-collapsed="true"] .nav-item,
|
|
27
|
+
.sidebar[data-collapsed="true"] .util-item,
|
|
28
|
+
.sidebar[data-collapsed="true"] .sidebar-brand-button,
|
|
29
|
+
.sidebar[data-collapsed="true"] .tree-folder-head,
|
|
30
|
+
.sidebar[data-collapsed="true"] .tree-session {
|
|
31
|
+
width: 28px;
|
|
32
|
+
height: 28px;
|
|
33
|
+
min-height: 28px;
|
|
34
|
+
padding: 0;
|
|
35
|
+
margin: 0 auto;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
gap: 0;
|
|
38
|
+
}
|
|
39
|
+
.sidebar[data-collapsed="true"] .sidebar-top {
|
|
40
|
+
padding: 0;
|
|
41
|
+
min-height: 40px;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
}
|
|
44
|
+
.sidebar[data-collapsed="true"] .sidebar-top .collapse-toggle {
|
|
45
|
+
width: 40px;
|
|
46
|
+
height: 40px;
|
|
47
|
+
min-height: 40px;
|
|
48
|
+
margin: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.sidebar-brand {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: var(--s-2);
|
|
55
|
+
padding: 0 var(--s-2);
|
|
56
|
+
min-height: 28px;
|
|
57
|
+
height: 28px;
|
|
58
|
+
}
|
|
59
|
+
.brand-mark {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: center;
|
|
63
|
+
width: 20px;
|
|
64
|
+
height: 20px;
|
|
65
|
+
flex: 0 0 20px;
|
|
66
|
+
background: transparent;
|
|
67
|
+
color: var(--accent);
|
|
68
|
+
}
|
|
69
|
+
.brand-mark svg { display: block; width: 20px; height: 20px; }
|
|
70
|
+
.brand-name {
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
letter-spacing: -0.02em;
|
|
74
|
+
color: var(--ink);
|
|
75
|
+
white-space: nowrap;
|
|
76
|
+
opacity: 1;
|
|
77
|
+
line-height: 1;
|
|
78
|
+
transition: opacity .15s ease;
|
|
79
|
+
}
|
|
80
|
+
.brand-dot { color: var(--accent); }
|
|
81
|
+
.sidebar[data-collapsed="true"] .brand-name { display: none; }
|
|
82
|
+
.sidebar[data-collapsed="true"] .brand-version { display: none; }
|
|
83
|
+
.sidebar[data-collapsed="true"] .sidebar-brand { justify-content: center; padding-left: 0; padding-right: 0; }
|
|
84
|
+
|
|
85
|
+
.sidebar-nav {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
gap: 2px;
|
|
89
|
+
flex: 0 0 auto;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.nav-item, .util-item {
|
|
93
|
+
appearance: none;
|
|
94
|
+
background: transparent;
|
|
95
|
+
border: 0;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: var(--s-3);
|
|
99
|
+
width: 100%;
|
|
100
|
+
padding: 0 12px;
|
|
101
|
+
min-height: 42px;
|
|
102
|
+
border-radius: var(--r-sm);
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
color: var(--ink);
|
|
105
|
+
font-family: var(--body);
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
font-weight: 400;
|
|
108
|
+
text-align: left;
|
|
109
|
+
transition: background .12s ease, color .12s ease;
|
|
110
|
+
position: relative;
|
|
111
|
+
}
|
|
112
|
+
.nav-item:hover, .util-item:hover {
|
|
113
|
+
background: var(--sidebar-hover);
|
|
114
|
+
}
|
|
115
|
+
.nav-item[aria-selected="true"] {
|
|
116
|
+
background: var(--sidebar-active);
|
|
117
|
+
color: var(--ink);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Unsaved-changes dot next to nav label */
|
|
121
|
+
.nav-item.has-changes::after {
|
|
122
|
+
content: "";
|
|
123
|
+
position: absolute;
|
|
124
|
+
right: 10px;
|
|
125
|
+
top: 50%;
|
|
126
|
+
transform: translateY(-50%);
|
|
127
|
+
width: 7px;
|
|
128
|
+
height: 7px;
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
background: var(--ink);
|
|
131
|
+
box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.30);
|
|
132
|
+
animation: dirty-pulse 2s ease-in-out infinite;
|
|
133
|
+
}
|
|
134
|
+
@keyframes dirty-pulse {
|
|
135
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.22); }
|
|
136
|
+
50% { box-shadow: 0 0 0 4px rgba(26, 24, 21, 0); }
|
|
137
|
+
}
|
|
138
|
+
.sidebar[data-collapsed="true"] .nav-item.has-changes::after {
|
|
139
|
+
right: auto;
|
|
140
|
+
top: 6px;
|
|
141
|
+
left: 28px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.nav-icon {
|
|
145
|
+
display: inline-flex;
|
|
146
|
+
width: 20px;
|
|
147
|
+
height: 20px;
|
|
148
|
+
flex: 0 0 20px;
|
|
149
|
+
color: currentColor;
|
|
150
|
+
}
|
|
151
|
+
.nav-icon svg { width: 100%; height: 100%; }
|
|
152
|
+
.nav-label {
|
|
153
|
+
white-space: nowrap;
|
|
154
|
+
opacity: 1;
|
|
155
|
+
transition: opacity .15s ease;
|
|
156
|
+
flex: 1;
|
|
157
|
+
}
|
|
158
|
+
.sidebar[data-collapsed="true"] .nav-label { opacity: 0; pointer-events: none; }
|
|
159
|
+
/* Collapsed sidebar (60px wide): hide the label entirely so the icon
|
|
160
|
+
centers in the narrow column instead of being pushed off-screen by
|
|
161
|
+
the still-laid-out (but invisible) label text. Same for the badge. */
|
|
162
|
+
.sidebar[data-collapsed="true"] .nav-label,
|
|
163
|
+
.sidebar[data-collapsed="true"] .nav-badge {
|
|
164
|
+
display: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.nav-badge {
|
|
168
|
+
font-family: var(--mono);
|
|
169
|
+
font-size: 10.5px;
|
|
170
|
+
background: var(--border-soft);
|
|
171
|
+
color: var(--ink-muted);
|
|
172
|
+
padding: 1px 6px;
|
|
173
|
+
border-radius: 4px;
|
|
174
|
+
font-variant-numeric: tabular-nums;
|
|
175
|
+
opacity: 1;
|
|
176
|
+
transition: opacity .15s ease;
|
|
177
|
+
}
|
|
178
|
+
.sidebar[data-collapsed="true"] .nav-badge { opacity: 0; }
|
|
179
|
+
.nav-item[aria-selected="true"] .nav-badge {
|
|
180
|
+
background: var(--bg-elev);
|
|
181
|
+
color: var(--ink-mid);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.sidebar-divider {
|
|
185
|
+
margin: var(--s-3) var(--s-2);
|
|
186
|
+
border-top: 1px solid var(--border);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.sidebar-utility {
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
gap: 2px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.sidebar-foot {
|
|
196
|
+
margin-top: auto;
|
|
197
|
+
padding-top: 2px;
|
|
198
|
+
display: flex;
|
|
199
|
+
flex-direction: row;
|
|
200
|
+
justify-content: flex-end;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: 2px;
|
|
203
|
+
}
|
|
204
|
+
/* Brand block at the bottom of the sidebar. Clickable: navigates to
|
|
205
|
+
About. Matches nav-item height so collapsed sidebar reads as a
|
|
206
|
+
uniform column of equally-sized icons. */
|
|
207
|
+
.sidebar-brand-button {
|
|
208
|
+
appearance: none;
|
|
209
|
+
background: transparent;
|
|
210
|
+
border: 0;
|
|
211
|
+
display: flex;
|
|
212
|
+
align-items: center;
|
|
213
|
+
gap: 8px;
|
|
214
|
+
width: 100%;
|
|
215
|
+
padding: 0 var(--s-2);
|
|
216
|
+
min-height: 28px;
|
|
217
|
+
height: 28px;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
text-align: left;
|
|
221
|
+
font: inherit;
|
|
222
|
+
color: var(--ink);
|
|
223
|
+
transition: background .12s;
|
|
224
|
+
}
|
|
225
|
+
.sidebar-brand-button[aria-selected="true"],
|
|
226
|
+
.sidebar-brand-button[aria-selected="true"]:hover {
|
|
227
|
+
background: var(--sidebar-active);
|
|
228
|
+
font-weight: 500;
|
|
229
|
+
}
|
|
230
|
+
.sidebar-brand-button:hover { background: var(--sidebar-hover); }
|
|
231
|
+
.sidebar-foot .brand-mark {
|
|
232
|
+
width: 14px;
|
|
233
|
+
height: 14px;
|
|
234
|
+
flex: 0 0 14px;
|
|
235
|
+
display: inline-flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
/* Lock the SVG box exactly to 14×14 so flex centering doesn't fight
|
|
239
|
+
the SVG's intrinsic dimensions; otherwise the mark drifts a couple
|
|
240
|
+
pixels low because <svg width=32 height=32> wants more vertical
|
|
241
|
+
space than the 14px row gives it. */
|
|
242
|
+
line-height: 0;
|
|
243
|
+
}
|
|
244
|
+
.sidebar-foot .brand-mark svg {
|
|
245
|
+
width: 14px;
|
|
246
|
+
height: 14px;
|
|
247
|
+
display: block;
|
|
248
|
+
}
|
|
249
|
+
.sidebar-foot .brand-name {
|
|
250
|
+
font-size: 13px;
|
|
251
|
+
font-weight: 400;
|
|
252
|
+
line-height: 1;
|
|
253
|
+
}
|
|
254
|
+
.brand-version {
|
|
255
|
+
margin-left: auto;
|
|
256
|
+
font-family: var(--mono);
|
|
257
|
+
font-size: 10px;
|
|
258
|
+
color: var(--ink-muted);
|
|
259
|
+
font-variant-numeric: tabular-nums;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Top strip · single row, currently hosts only the collapse toggle on
|
|
263
|
+
the right. Matches the page-title-bar height so the topmost row of
|
|
264
|
+
the window reads as one unified band. */
|
|
265
|
+
.sidebar-top {
|
|
266
|
+
display: flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
padding: 0;
|
|
269
|
+
min-height: 40px;
|
|
270
|
+
/* Sit flush above the first nav item — no extra breathing room
|
|
271
|
+
between the brand strip and the nav list. */
|
|
272
|
+
margin-bottom: 0;
|
|
273
|
+
}
|
|
274
|
+
.collapse-toggle {
|
|
275
|
+
appearance: none;
|
|
276
|
+
background: transparent;
|
|
277
|
+
border: 0;
|
|
278
|
+
display: inline-flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
justify-content: center;
|
|
281
|
+
width: 40px;
|
|
282
|
+
height: 40px;
|
|
283
|
+
padding: 0;
|
|
284
|
+
border-radius: 4px;
|
|
285
|
+
cursor: pointer;
|
|
286
|
+
color: var(--ink);
|
|
287
|
+
transition: background .12s;
|
|
288
|
+
min-height: 0;
|
|
289
|
+
flex: 0 0 40px;
|
|
290
|
+
}
|
|
291
|
+
.collapse-toggle:hover { background: var(--sidebar-hover); }
|
|
292
|
+
.collapse-toggle .nav-icon {
|
|
293
|
+
width: 14px;
|
|
294
|
+
height: 14px;
|
|
295
|
+
flex: 0 0 14px;
|
|
296
|
+
}
|
|
297
|
+
.sidebar[data-collapsed="true"] .sidebar-top {
|
|
298
|
+
justify-content: center;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* chevron flips when the sidebar is collapsed; the rest is util-item styling */
|
|
302
|
+
.collapse-toggle .nav-icon {
|
|
303
|
+
transition: transform .25s cubic-bezier(.4, 0, .2, 1);
|
|
304
|
+
}
|
|
305
|
+
.sidebar[data-collapsed="true"] .collapse-toggle .nav-icon {
|
|
306
|
+
transform: rotate(180deg);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Drag-to-resize handle. Sits absolutely against the sidebar's right
|
|
310
|
+
border so the cursor target spans the full height. 6px wide hit area
|
|
311
|
+
centered on the visible 1px border — easy to grab without bumping
|
|
312
|
+
into adjacent layout. */
|
|
313
|
+
.sidebar-resize-handle {
|
|
314
|
+
position: absolute;
|
|
315
|
+
top: 0;
|
|
316
|
+
right: -3px;
|
|
317
|
+
width: 6px;
|
|
318
|
+
height: 100%;
|
|
319
|
+
cursor: col-resize;
|
|
320
|
+
z-index: 5;
|
|
321
|
+
touch-action: none;
|
|
322
|
+
/* Subtle hover indicator: deepens the border-right color on hover so the
|
|
323
|
+
user knows the edge is interactive. */
|
|
324
|
+
background: transparent;
|
|
325
|
+
transition: background .12s ease;
|
|
326
|
+
}
|
|
327
|
+
.sidebar-resize-handle:hover,
|
|
328
|
+
body.is-resizing-sidebar .sidebar-resize-handle {
|
|
329
|
+
/* Wash the hit area in the accent color so the user knows the edge is
|
|
330
|
+
live and so the theme follows their accent choice. */
|
|
331
|
+
background: var(--accent-soft);
|
|
332
|
+
}
|
|
333
|
+
/* While dragging, freeze global cursor + suppress text selection so the
|
|
334
|
+
whole page tracks resize cleanly even if pointer leaves the handle. */
|
|
335
|
+
body.is-resizing-sidebar {
|
|
336
|
+
cursor: col-resize !important;
|
|
337
|
+
user-select: none;
|
|
338
|
+
}
|
|
339
|
+
body.is-resizing-sidebar * {
|
|
340
|
+
cursor: col-resize !important;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* === v1.0 codex-style sidebar tree === */
|
|
344
|
+
|
|
345
|
+
/* Compact top nav: smaller height + smaller font, so the folder tree
|
|
346
|
+
below dominates. */
|
|
347
|
+
/* Match the dimensions of .tree-folder-head + .tree-session below so
|
|
348
|
+
the top nav, the folder head, and the session rows form one visually
|
|
349
|
+
continuous column: same left padding (icon at x=8), same icon-label
|
|
350
|
+
gap, same row height, same corner radius. Without this the nav
|
|
351
|
+
icons sit 2px further right than the folder icons and the rows are
|
|
352
|
+
noticeably taller. */
|
|
353
|
+
.sidebar-nav.compact .nav-item {
|
|
354
|
+
font-size: 13px;
|
|
355
|
+
padding: 4px 8px;
|
|
356
|
+
min-height: 28px;
|
|
357
|
+
gap: 8px;
|
|
358
|
+
border-radius: 4px;
|
|
359
|
+
position: relative;
|
|
360
|
+
letter-spacing: -0.005em;
|
|
361
|
+
transition: background .14s ease, color .14s ease;
|
|
362
|
+
}
|
|
363
|
+
.sidebar-nav.compact .nav-item:hover { background: var(--sidebar-hover); }
|
|
364
|
+
.sidebar-nav.compact .nav-icon {
|
|
365
|
+
width: 14px;
|
|
366
|
+
height: 14px;
|
|
367
|
+
flex: 0 0 14px;
|
|
368
|
+
}
|
|
369
|
+
.sidebar[data-collapsed="true"] .sidebar-nav.compact .nav-item {
|
|
370
|
+
justify-content: center;
|
|
371
|
+
gap: 0;
|
|
372
|
+
padding: 8px 0;
|
|
373
|
+
}
|
|
374
|
+
.sidebar-nav.compact .nav-item.is-active {
|
|
375
|
+
background: var(--sidebar-active);
|
|
376
|
+
color: var(--ink);
|
|
377
|
+
font-weight: 500;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/* Tree section header. Looks like codex: uppercase label, small +
|
|
381
|
+
button on hover. */
|
|
382
|
+
.tree {
|
|
383
|
+
margin-top: var(--sidebar-section-gap);
|
|
384
|
+
display: flex;
|
|
385
|
+
flex-direction: column;
|
|
386
|
+
gap: 2px;
|
|
387
|
+
/* Only vertical scroll when the tree is taller than the rail.
|
|
388
|
+
overflow-y alone leaves overflow-x at the default `auto`, which
|
|
389
|
+
gives us an unwanted horizontal scrollbar whenever a session
|
|
390
|
+
title (or its hover row padding) brushes the viewport edge.
|
|
391
|
+
overflow-x:hidden truncates with ellipsis instead — labels
|
|
392
|
+
already do `text-overflow: ellipsis`. */
|
|
393
|
+
overflow-y: auto;
|
|
394
|
+
overflow-x: hidden;
|
|
395
|
+
flex: 1;
|
|
396
|
+
min-height: 0;
|
|
397
|
+
padding-bottom: var(--s-3);
|
|
398
|
+
}
|
|
399
|
+
.tree-head {
|
|
400
|
+
display: flex;
|
|
401
|
+
justify-content: space-between;
|
|
402
|
+
align-items: center;
|
|
403
|
+
/* Right padding matches .tree-session so the +-button glyph
|
|
404
|
+
right-aligns with the per-row tree-meta timestamp below. */
|
|
405
|
+
padding: 0 8px;
|
|
406
|
+
min-height: 24px;
|
|
407
|
+
height: 24px;
|
|
408
|
+
font-size: 12px;
|
|
409
|
+
font-weight: 500;
|
|
410
|
+
letter-spacing: 0;
|
|
411
|
+
color: var(--ink-mid);
|
|
412
|
+
/* No margin-bottom — let the parent .tree's `gap: 2px` carry the
|
|
413
|
+
space below, matching what sits between folder rows. The big gap
|
|
414
|
+
above "Sessions" still comes from .tree margin-top. */
|
|
415
|
+
}
|
|
416
|
+
.tree-head-action {
|
|
417
|
+
appearance: none;
|
|
418
|
+
background: transparent;
|
|
419
|
+
border: 0;
|
|
420
|
+
/* Zero right padding so the glyph itself lands flush against the
|
|
421
|
+
row's 8px right inset — same column as the tree-meta text on
|
|
422
|
+
each session row. */
|
|
423
|
+
padding: 2px 0 2px 4px;
|
|
424
|
+
border-radius: 4px;
|
|
425
|
+
cursor: pointer;
|
|
426
|
+
color: var(--ink);
|
|
427
|
+
display: inline-flex;
|
|
428
|
+
align-items: center;
|
|
429
|
+
opacity: 0;
|
|
430
|
+
transition: opacity .12s, background .12s;
|
|
431
|
+
}
|
|
432
|
+
.tree-head:hover .tree-head-action,
|
|
433
|
+
.tree-head-action:focus-visible { opacity: 0.7; }
|
|
434
|
+
.tree-head:hover .tree-head-action:hover { opacity: 1; background: var(--sidebar-hover); }
|
|
435
|
+
.tree-head-action svg { width: 14px; height: 14px; }
|
|
436
|
+
|
|
437
|
+
/* Folder grouping. Chevron rotates on expand. */
|
|
438
|
+
.tree-folder {
|
|
439
|
+
display: flex;
|
|
440
|
+
flex-direction: column;
|
|
441
|
+
}
|
|
442
|
+
.tree-folder[data-dnd-over="true"] > .tree-folder-head {
|
|
443
|
+
box-shadow: 0 -2px 0 var(--accent) inset;
|
|
444
|
+
}
|
|
445
|
+
/* Session being dragged → folder is a drop target. Tint the folder
|
|
446
|
+
head + outline the whole folder so the user knows where it'll land,
|
|
447
|
+
independent of whether the folder is expanded or collapsed. */
|
|
448
|
+
.tree-folder.is-session-drop-target {
|
|
449
|
+
border-radius: 6px;
|
|
450
|
+
outline: 1px dashed var(--ink-mid);
|
|
451
|
+
outline-offset: -2px;
|
|
452
|
+
background: var(--sidebar-hover);
|
|
453
|
+
}
|
|
454
|
+
.tree-session[draggable="true"] { cursor: pointer; }
|
|
455
|
+
.tree-session[draggable="true"]:active { cursor: grabbing; }
|
|
456
|
+
.tree-folder-head[draggable="true"] {
|
|
457
|
+
cursor: grab;
|
|
458
|
+
}
|
|
459
|
+
.tree-folder-head[draggable="true"]:active {
|
|
460
|
+
cursor: grabbing;
|
|
461
|
+
}
|
|
462
|
+
.tree-folder-head {
|
|
463
|
+
appearance: none;
|
|
464
|
+
background: transparent;
|
|
465
|
+
border: 0;
|
|
466
|
+
width: 100%;
|
|
467
|
+
text-align: left;
|
|
468
|
+
padding: 4px 8px;
|
|
469
|
+
display: flex;
|
|
470
|
+
align-items: center;
|
|
471
|
+
gap: 8px;
|
|
472
|
+
font-size: 13px;
|
|
473
|
+
font-weight: 400;
|
|
474
|
+
color: var(--ink);
|
|
475
|
+
cursor: pointer;
|
|
476
|
+
border-radius: 4px;
|
|
477
|
+
font-family: var(--body);
|
|
478
|
+
min-height: 28px;
|
|
479
|
+
}
|
|
480
|
+
.tree-folder-head:hover { background: var(--sidebar-hover); }
|
|
481
|
+
.tree-folder-icon {
|
|
482
|
+
display: inline-flex;
|
|
483
|
+
width: 14px;
|
|
484
|
+
height: 14px;
|
|
485
|
+
flex: 0 0 14px;
|
|
486
|
+
color: var(--ink);
|
|
487
|
+
}
|
|
488
|
+
.tree-folder-icon svg { width: 100%; height: 100%; }
|
|
489
|
+
.tree-folder-head.is-open .tree-folder-icon { color: var(--ink); }
|
|
490
|
+
.tree-folder-name {
|
|
491
|
+
flex: 1;
|
|
492
|
+
white-space: nowrap;
|
|
493
|
+
overflow: hidden;
|
|
494
|
+
text-overflow: ellipsis;
|
|
495
|
+
}
|
|
496
|
+
.tree-folder-count {
|
|
497
|
+
font-size: 13px;
|
|
498
|
+
color: var(--ink);
|
|
499
|
+
font-variant-numeric: tabular-nums;
|
|
500
|
+
padding: 0 5px;
|
|
501
|
+
background: transparent;
|
|
502
|
+
border-radius: 999px;
|
|
503
|
+
opacity: 0.6;
|
|
504
|
+
}
|
|
505
|
+
.tree-folder-actions {
|
|
506
|
+
display: none;
|
|
507
|
+
gap: 2px;
|
|
508
|
+
}
|
|
509
|
+
/* Same touch carve-out as session-actions above — don't reveal folder
|
|
510
|
+
rename/delete on a tap-emulated hover. */
|
|
511
|
+
@media (hover: hover) and (pointer: fine) {
|
|
512
|
+
.tree-folder-head:hover .tree-folder-actions { display: inline-flex; }
|
|
513
|
+
}
|
|
514
|
+
.tree-folder-action {
|
|
515
|
+
appearance: none;
|
|
516
|
+
background: transparent;
|
|
517
|
+
border: 0;
|
|
518
|
+
display: inline-flex;
|
|
519
|
+
align-items: center;
|
|
520
|
+
justify-content: center;
|
|
521
|
+
width: 18px;
|
|
522
|
+
height: 18px;
|
|
523
|
+
padding: 0;
|
|
524
|
+
font-size: 13px;
|
|
525
|
+
color: var(--ink);
|
|
526
|
+
cursor: pointer;
|
|
527
|
+
opacity: 0.6;
|
|
528
|
+
border-radius: 3px;
|
|
529
|
+
}
|
|
530
|
+
.tree-folder-action:hover { opacity: 1; background: var(--sidebar-hover); }
|
|
531
|
+
.tree-folder-action svg { width: 12px; height: 12px; }
|
|
532
|
+
|
|
533
|
+
.tree-folder-body {
|
|
534
|
+
display: flex;
|
|
535
|
+
flex-direction: column;
|
|
536
|
+
/* No left padding here — the session rows themselves get padding-left
|
|
537
|
+
that lines their label up under the folder name. Keeping the bg
|
|
538
|
+
extend across the full sidebar width when selected/hovered. */
|
|
539
|
+
padding-left: 0;
|
|
540
|
+
}
|
|
541
|
+
.tree-empty {
|
|
542
|
+
font-size: 13px;
|
|
543
|
+
color: var(--ink);
|
|
544
|
+
padding: 3px 8px;
|
|
545
|
+
font-style: italic;
|
|
546
|
+
opacity: 0.5;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* Session rows. Codex uses a colored dot + truncated label + tiny
|
|
550
|
+
timestamp on the right. */
|
|
551
|
+
.tree-session {
|
|
552
|
+
display: flex;
|
|
553
|
+
align-items: center;
|
|
554
|
+
gap: 8px;
|
|
555
|
+
/* Match the folder head: 8px left padding, then a 14px icon column
|
|
556
|
+
(the colored dot lives here, sized 8px and centered), then 8px gap
|
|
557
|
+
before the label — lines the label up exactly under the folder name. */
|
|
558
|
+
padding: 4px 8px;
|
|
559
|
+
border-radius: 4px;
|
|
560
|
+
cursor: pointer;
|
|
561
|
+
font-size: 13px;
|
|
562
|
+
color: var(--ink);
|
|
563
|
+
font-family: var(--body);
|
|
564
|
+
user-select: none;
|
|
565
|
+
transition: background .1s;
|
|
566
|
+
min-height: 28px;
|
|
567
|
+
}
|
|
568
|
+
.tree-session:hover { background: var(--sidebar-hover); }
|
|
569
|
+
.tree-session.is-active {
|
|
570
|
+
background: var(--sidebar-active);
|
|
571
|
+
font-weight: 500;
|
|
572
|
+
}
|
|
573
|
+
/* Drop-line shown above the row when a sibling session is being
|
|
574
|
+
dragged over it for within-folder reorder. Top-only inset shadow
|
|
575
|
+
reads as a 2px insertion mark without shifting layout. */
|
|
576
|
+
.tree-session.is-reorder-target {
|
|
577
|
+
position: relative;
|
|
578
|
+
}
|
|
579
|
+
.tree-session.is-reorder-target::before {
|
|
580
|
+
content: "";
|
|
581
|
+
position: absolute;
|
|
582
|
+
top: -1px;
|
|
583
|
+
left: 4px;
|
|
584
|
+
right: 4px;
|
|
585
|
+
height: 2px;
|
|
586
|
+
background: var(--ink-mid);
|
|
587
|
+
border-radius: 1px;
|
|
588
|
+
pointer-events: none;
|
|
589
|
+
}
|
|
590
|
+
/* Status dot · deliberately understated. The earlier version had a
|
|
591
|
+
green dot + soft glow + expanding halo pulse; in a sidebar with
|
|
592
|
+
eight running sessions it read as a row of strobing alerts. Now:
|
|
593
|
+
one 5px dot, no halo, no shadow, no animation. Color alone carries
|
|
594
|
+
running vs stopped. */
|
|
595
|
+
.tree-dot {
|
|
596
|
+
width: 14px;
|
|
597
|
+
height: 14px;
|
|
598
|
+
flex: 0 0 14px;
|
|
599
|
+
display: inline-flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
justify-content: center;
|
|
602
|
+
}
|
|
603
|
+
.tree-dot::after {
|
|
604
|
+
content: "";
|
|
605
|
+
width: 7px;
|
|
606
|
+
height: 7px;
|
|
607
|
+
border-radius: 50%;
|
|
608
|
+
background: var(--ink-faint);
|
|
609
|
+
transition: background .15s ease;
|
|
610
|
+
}
|
|
611
|
+
.tree-session.is-running .tree-dot::after {
|
|
612
|
+
background: var(--green);
|
|
613
|
+
/* Soft halo so the dot reads as "alive" even from across the sidebar.
|
|
614
|
+
Box-shadow uses currentColor isn't ideal here (the dot itself uses
|
|
615
|
+
background, not color), so we hardcode each state's halo color
|
|
616
|
+
below. */
|
|
617
|
+
box-shadow: 0 0 0 0 rgba(74, 138, 74, 0.55);
|
|
618
|
+
animation: tree-dot-breathe-idle 2.8s ease-in-out infinite;
|
|
619
|
+
}
|
|
620
|
+
/* Working = CLI is actively writing to its transcript (i.e. thinking
|
|
621
|
+
or printing tokens). Idle stays green + slow breathe; working flips
|
|
622
|
+
to blue + faster, more obvious breathe. */
|
|
623
|
+
.tree-session.is-running.is-working .tree-dot::after {
|
|
624
|
+
background: var(--blue, #4a73a5);
|
|
625
|
+
box-shadow: 0 0 0 0 rgba(74, 115, 165, 0.65);
|
|
626
|
+
animation: tree-dot-breathe-working 1.4s ease-in-out infinite;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
@keyframes tree-dot-breathe-idle {
|
|
630
|
+
0%, 100% {
|
|
631
|
+
box-shadow: 0 0 0 0 rgba(74, 138, 74, 0.45);
|
|
632
|
+
opacity: 0.85;
|
|
633
|
+
}
|
|
634
|
+
50% {
|
|
635
|
+
box-shadow: 0 0 0 4px rgba(74, 138, 74, 0);
|
|
636
|
+
opacity: 1;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
@keyframes tree-dot-breathe-working {
|
|
640
|
+
0%, 100% {
|
|
641
|
+
box-shadow: 0 0 0 0 rgba(74, 115, 165, 0.65);
|
|
642
|
+
opacity: 0.9;
|
|
643
|
+
transform: scale(1);
|
|
644
|
+
}
|
|
645
|
+
50% {
|
|
646
|
+
box-shadow: 0 0 0 5px rgba(74, 115, 165, 0);
|
|
647
|
+
opacity: 1;
|
|
648
|
+
transform: scale(1.15);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/* Respect users who've asked for less motion — keep the color signal
|
|
652
|
+
but drop the pulse. */
|
|
653
|
+
@media (prefers-reduced-motion: reduce) {
|
|
654
|
+
.tree-session.is-running .tree-dot::after,
|
|
655
|
+
.tree-session.is-running.is-working .tree-dot::after {
|
|
656
|
+
animation: none;
|
|
657
|
+
box-shadow: none;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
.tree-label {
|
|
661
|
+
flex: 1;
|
|
662
|
+
white-space: nowrap;
|
|
663
|
+
overflow: hidden;
|
|
664
|
+
text-overflow: ellipsis;
|
|
665
|
+
}
|
|
666
|
+
.tree-session-actions {
|
|
667
|
+
display: none;
|
|
668
|
+
gap: 2px;
|
|
669
|
+
flex-shrink: 0;
|
|
670
|
+
}
|
|
671
|
+
/* Hover-only on real-pointer devices. On touch devices the first tap
|
|
672
|
+
emulates :hover (revealing rename/delete), which means the user has
|
|
673
|
+
to tap a second time to actually open the session. (hover: hover)
|
|
674
|
+
matches a real mouse / trackpad; (pointer: fine) further requires
|
|
675
|
+
precise cursor (so hybrid touch+mouse laptops with a stylus get
|
|
676
|
+
hover but pure-touch phones / tablets do not.) Phones keep the
|
|
677
|
+
timestamp visible and never reveal the action buttons here — they
|
|
678
|
+
can use the kebab in the session pane's top bar instead. */
|
|
679
|
+
@media (hover: hover) and (pointer: fine) {
|
|
680
|
+
.tree-session:hover .tree-session-actions { display: inline-flex; }
|
|
681
|
+
.tree-session:hover .tree-meta { display: none; }
|
|
682
|
+
}
|
|
683
|
+
.tree-session-action {
|
|
684
|
+
appearance: none;
|
|
685
|
+
background: transparent;
|
|
686
|
+
border: 0;
|
|
687
|
+
display: inline-flex;
|
|
688
|
+
align-items: center;
|
|
689
|
+
justify-content: center;
|
|
690
|
+
width: 18px;
|
|
691
|
+
height: 18px;
|
|
692
|
+
padding: 0;
|
|
693
|
+
color: var(--ink);
|
|
694
|
+
cursor: pointer;
|
|
695
|
+
opacity: 0.6;
|
|
696
|
+
border-radius: 3px;
|
|
697
|
+
}
|
|
698
|
+
.tree-session-action:hover { opacity: 1; background: var(--sidebar-hover); }
|
|
699
|
+
.tree-session-action svg { width: 12px; height: 12px; }
|
|
700
|
+
.tree-meta {
|
|
701
|
+
font-size: 12px;
|
|
702
|
+
color: var(--ink);
|
|
703
|
+
font-variant-numeric: tabular-nums;
|
|
704
|
+
flex-shrink: 0;
|
|
705
|
+
opacity: 0.55;
|
|
706
|
+
letter-spacing: 0.01em;
|
|
707
|
+
}
|