@bakapiano/ccsm 0.6.0 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -38
- package/bin/ccsm.js +194 -0
- package/lib/favorites.js +23 -45
- package/lib/jsonStore.js +60 -0
- package/lib/labels.js +21 -41
- package/lib/webTerminal.js +173 -0
- package/package.json +11 -3
- package/public/css/base.css +82 -0
- package/public/css/cards.css +149 -0
- package/public/css/feedback.css +219 -0
- package/public/css/forms.css +282 -0
- package/public/css/layout.css +107 -0
- package/public/css/modal.css +169 -0
- package/public/css/responsive.css +10 -0
- package/public/css/sidebar.css +165 -0
- package/public/css/tables.css +266 -0
- package/public/css/terminals.css +112 -0
- package/public/css/tokens.css +63 -0
- package/public/css/wco.css +70 -0
- package/public/css/widgets.css +204 -0
- package/public/favicon.svg +1 -1
- package/public/index.html +52 -490
- package/public/js/actions.js +87 -0
- package/public/js/api.js +103 -0
- package/public/js/backend.js +28 -0
- package/public/js/components/App.js +45 -0
- package/public/js/components/Card.js +24 -0
- package/public/js/components/DialogHost.js +45 -0
- package/public/js/components/Fab.js +11 -0
- package/public/js/components/FavoritesTable.js +81 -0
- package/public/js/components/Footer.js +12 -0
- package/public/js/components/NewSessionModal.js +142 -0
- package/public/js/components/OfflineBanner.js +52 -0
- package/public/js/components/PageHead.js +33 -0
- package/public/js/components/Pagination.js +27 -0
- package/public/js/components/ProgressList.js +32 -0
- package/public/js/components/RecentTable.js +68 -0
- package/public/js/components/RepoPicker.js +40 -0
- package/public/js/components/ReposEditor.js +74 -0
- package/public/js/components/ServerStatus.js +18 -0
- package/public/js/components/SessionsTable.js +71 -0
- package/public/js/components/Sidebar.js +52 -0
- package/public/js/components/SnapshotPanel.js +77 -0
- package/public/js/components/TerminalView.js +108 -0
- package/public/js/components/TitleCell.js +40 -0
- package/public/js/components/Toast.js +8 -0
- package/public/js/components/WorkspacePicker.js +19 -0
- package/public/js/components/WorkspacesGrid.js +41 -0
- package/public/js/dialog.js +59 -0
- package/public/js/html.js +6 -0
- package/public/js/icons.js +114 -0
- package/public/js/main.js +81 -0
- package/public/js/pages/AboutPage.js +85 -0
- package/public/js/pages/ConfigurePage.js +194 -0
- package/public/js/pages/LaunchPage.js +117 -0
- package/public/js/pages/SessionsPage.js +47 -0
- package/public/js/pages/TerminalsPage.js +74 -0
- package/public/js/state.js +87 -0
- package/public/js/streaming.js +96 -0
- package/public/js/toast.js +14 -0
- package/public/js/util.js +24 -0
- package/public/manifest.webmanifest +14 -0
- package/scripts/install.js +111 -0
- package/scripts/uninstall.js +56 -0
- package/server.js +286 -30
- package/public/app.js +0 -1353
- package/public/styles.css +0 -1639
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* Left collapsible sidebar nav · brand mark · util items · collapse toggle */
|
|
2
|
+
|
|
3
|
+
.sidebar {
|
|
4
|
+
position: sticky;
|
|
5
|
+
top: 0;
|
|
6
|
+
height: 100vh;
|
|
7
|
+
background: var(--sidebar-bg);
|
|
8
|
+
border-right: 1px solid var(--border);
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
padding: var(--s-4) var(--s-3);
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
transition: padding .25s cubic-bezier(.4, 0, .2, 1);
|
|
14
|
+
}
|
|
15
|
+
.sidebar[data-collapsed="true"] {
|
|
16
|
+
padding: var(--s-4) var(--s-2);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sidebar-brand {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: var(--s-2);
|
|
23
|
+
/* Top padding matches .main's so the brand mark sits on the same
|
|
24
|
+
horizontal band as the page-title. */
|
|
25
|
+
padding: var(--s-4) var(--s-2);
|
|
26
|
+
min-height: 56px;
|
|
27
|
+
}
|
|
28
|
+
.brand-mark {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
width: 32px;
|
|
33
|
+
height: 32px;
|
|
34
|
+
flex: 0 0 32px;
|
|
35
|
+
background: transparent;
|
|
36
|
+
}
|
|
37
|
+
.brand-mark svg { display: block; }
|
|
38
|
+
.brand-name {
|
|
39
|
+
font-size: 19px;
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
letter-spacing: -0.02em;
|
|
42
|
+
color: var(--ink);
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
opacity: 1;
|
|
45
|
+
transition: opacity .15s ease;
|
|
46
|
+
}
|
|
47
|
+
.brand-dot { color: var(--accent); }
|
|
48
|
+
.sidebar[data-collapsed="true"] .brand-name { opacity: 0; pointer-events: none; }
|
|
49
|
+
|
|
50
|
+
.sidebar-nav {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: 2px;
|
|
54
|
+
flex: 0 0 auto;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.nav-item, .util-item {
|
|
58
|
+
appearance: none;
|
|
59
|
+
background: transparent;
|
|
60
|
+
border: 0;
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: var(--s-3);
|
|
64
|
+
width: 100%;
|
|
65
|
+
padding: 0 12px;
|
|
66
|
+
min-height: 42px;
|
|
67
|
+
border-radius: var(--r-sm);
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
color: var(--ink-mid);
|
|
70
|
+
font-family: var(--body);
|
|
71
|
+
font-size: 14.5px;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
text-align: left;
|
|
74
|
+
transition: background .12s ease, color .12s ease;
|
|
75
|
+
position: relative;
|
|
76
|
+
}
|
|
77
|
+
.nav-item:hover, .util-item:hover {
|
|
78
|
+
background: var(--sidebar-hover);
|
|
79
|
+
color: var(--ink);
|
|
80
|
+
}
|
|
81
|
+
.nav-item[aria-selected="true"] {
|
|
82
|
+
background: var(--sidebar-active);
|
|
83
|
+
color: var(--ink);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Unsaved-changes dot next to nav label */
|
|
87
|
+
.nav-item.has-changes::after {
|
|
88
|
+
content: "";
|
|
89
|
+
position: absolute;
|
|
90
|
+
right: 10px;
|
|
91
|
+
top: 50%;
|
|
92
|
+
transform: translateY(-50%);
|
|
93
|
+
width: 7px;
|
|
94
|
+
height: 7px;
|
|
95
|
+
border-radius: 50%;
|
|
96
|
+
background: var(--ink);
|
|
97
|
+
box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.30);
|
|
98
|
+
animation: dirty-pulse 2s ease-in-out infinite;
|
|
99
|
+
}
|
|
100
|
+
@keyframes dirty-pulse {
|
|
101
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.22); }
|
|
102
|
+
50% { box-shadow: 0 0 0 4px rgba(26, 24, 21, 0); }
|
|
103
|
+
}
|
|
104
|
+
.sidebar[data-collapsed="true"] .nav-item.has-changes::after {
|
|
105
|
+
right: auto;
|
|
106
|
+
top: 6px;
|
|
107
|
+
left: 28px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.nav-icon {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
width: 20px;
|
|
113
|
+
height: 20px;
|
|
114
|
+
flex: 0 0 20px;
|
|
115
|
+
color: currentColor;
|
|
116
|
+
}
|
|
117
|
+
.nav-icon svg { width: 100%; height: 100%; }
|
|
118
|
+
.nav-label {
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
opacity: 1;
|
|
121
|
+
transition: opacity .15s ease;
|
|
122
|
+
flex: 1;
|
|
123
|
+
}
|
|
124
|
+
.sidebar[data-collapsed="true"] .nav-label { opacity: 0; pointer-events: none; }
|
|
125
|
+
|
|
126
|
+
.nav-badge {
|
|
127
|
+
font-family: var(--mono);
|
|
128
|
+
font-size: 10.5px;
|
|
129
|
+
background: var(--border-soft);
|
|
130
|
+
color: var(--ink-muted);
|
|
131
|
+
padding: 1px 6px;
|
|
132
|
+
border-radius: 4px;
|
|
133
|
+
font-variant-numeric: tabular-nums;
|
|
134
|
+
opacity: 1;
|
|
135
|
+
transition: opacity .15s ease;
|
|
136
|
+
}
|
|
137
|
+
.sidebar[data-collapsed="true"] .nav-badge { opacity: 0; }
|
|
138
|
+
.nav-item[aria-selected="true"] .nav-badge {
|
|
139
|
+
background: var(--bg-elev);
|
|
140
|
+
color: var(--ink-mid);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.sidebar-divider {
|
|
144
|
+
margin: var(--s-3) var(--s-2);
|
|
145
|
+
border-top: 1px solid var(--border);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.sidebar-utility {
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: 2px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.sidebar-foot {
|
|
155
|
+
margin-top: auto;
|
|
156
|
+
padding-top: var(--s-2);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* chevron flips when the sidebar is collapsed; the rest is util-item styling */
|
|
160
|
+
.collapse-toggle .nav-icon {
|
|
161
|
+
transition: transform .25s cubic-bezier(.4, 0, .2, 1);
|
|
162
|
+
}
|
|
163
|
+
.sidebar[data-collapsed="true"] .collapse-toggle .nav-icon {
|
|
164
|
+
transform: rotate(180deg);
|
|
165
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/* Data tables · sticky actions column · status mark · title/path cells ·
|
|
2
|
+
star + rename inline buttons · row stagger animation */
|
|
3
|
+
|
|
4
|
+
.table-scroll {
|
|
5
|
+
overflow-x: auto;
|
|
6
|
+
/* tactile: scroll-padding so the actions column edges aren't flush
|
|
7
|
+
against the viewport when you scroll right to reach the buttons */
|
|
8
|
+
scroll-padding-right: var(--s-6);
|
|
9
|
+
}
|
|
10
|
+
.table-scroll::-webkit-scrollbar { height: 8px; }
|
|
11
|
+
.table-scroll .data {
|
|
12
|
+
min-width: 960px;
|
|
13
|
+
table-layout: auto;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.data {
|
|
17
|
+
width: 100%;
|
|
18
|
+
border-collapse: collapse;
|
|
19
|
+
font-family: var(--body);
|
|
20
|
+
font-size: 13.5px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.data thead th {
|
|
24
|
+
padding: 10px var(--s-5) 10px;
|
|
25
|
+
text-align: left;
|
|
26
|
+
font-family: var(--body);
|
|
27
|
+
font-size: 11px;
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
letter-spacing: 0.04em;
|
|
30
|
+
color: var(--ink-muted);
|
|
31
|
+
border-bottom: 1px solid var(--border);
|
|
32
|
+
background: var(--bg);
|
|
33
|
+
vertical-align: bottom;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
}
|
|
36
|
+
.data thead th.num { text-align: right; }
|
|
37
|
+
/* shrink-to-fit pattern: width:1% with nowrap → the cell takes only
|
|
38
|
+
what it needs; title / path cells get the remainder */
|
|
39
|
+
.data thead th.col-mark { width: 28px; padding-left: 0; padding-right: 0; }
|
|
40
|
+
.data thead th.col-star { width: 28px; padding-left: 0; padding-right: 0; }
|
|
41
|
+
.data thead th.col-actions { width: 1%; white-space: nowrap; }
|
|
42
|
+
.data thead th.num { width: 1%; white-space: nowrap; }
|
|
43
|
+
.data tbody td.num { white-space: nowrap; }
|
|
44
|
+
.data tbody td:last-child { white-space: nowrap; }
|
|
45
|
+
.data thead th:first-child { padding-left: var(--s-6); }
|
|
46
|
+
.data thead th:last-child { padding-right: var(--s-6); }
|
|
47
|
+
|
|
48
|
+
/* Last column pins to the right edge of the .table-scroll container during
|
|
49
|
+
horizontal scroll. Backgrounds must be solid because sticky cells overlay
|
|
50
|
+
the content scrolling beneath them. */
|
|
51
|
+
.data thead th:last-child,
|
|
52
|
+
.data tbody td:last-child {
|
|
53
|
+
position: sticky;
|
|
54
|
+
right: 0;
|
|
55
|
+
z-index: 1;
|
|
56
|
+
box-shadow: -10px 0 10px -8px rgba(26, 24, 21, 0.08);
|
|
57
|
+
}
|
|
58
|
+
.data thead th:last-child { background: var(--bg); }
|
|
59
|
+
.data tbody td:last-child { background: var(--bg-elev); }
|
|
60
|
+
.data tbody tr:hover td:last-child { background: var(--bg); }
|
|
61
|
+
|
|
62
|
+
.data tbody tr {
|
|
63
|
+
border-bottom: 1px solid var(--border-soft);
|
|
64
|
+
transition: background .12s ease;
|
|
65
|
+
}
|
|
66
|
+
.data tbody tr:last-child { border-bottom: 0; }
|
|
67
|
+
.data tbody tr:hover { background: var(--bg); }
|
|
68
|
+
|
|
69
|
+
.data tbody td {
|
|
70
|
+
padding: 12px var(--s-5);
|
|
71
|
+
vertical-align: middle;
|
|
72
|
+
color: var(--ink);
|
|
73
|
+
}
|
|
74
|
+
.data tbody td:first-child { padding-left: var(--s-6); }
|
|
75
|
+
.data tbody td:last-child { padding-right: var(--s-6); }
|
|
76
|
+
.data tbody td.num {
|
|
77
|
+
text-align: right;
|
|
78
|
+
font-family: var(--mono);
|
|
79
|
+
font-size: 11.5px;
|
|
80
|
+
color: var(--ink-mid);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* JS adds .no-anim after the first render so 5s auto-refresh doesn't
|
|
84
|
+
restage the stagger and strobe. */
|
|
85
|
+
.data tbody:not(.no-anim) tr { animation: row-in .3s ease-out backwards; }
|
|
86
|
+
.data tbody:not(.no-anim) tr:nth-child(1) { animation-delay: 0ms; }
|
|
87
|
+
.data tbody:not(.no-anim) tr:nth-child(2) { animation-delay: 20ms; }
|
|
88
|
+
.data tbody:not(.no-anim) tr:nth-child(3) { animation-delay: 40ms; }
|
|
89
|
+
.data tbody:not(.no-anim) tr:nth-child(4) { animation-delay: 60ms; }
|
|
90
|
+
.data tbody:not(.no-anim) tr:nth-child(5) { animation-delay: 80ms; }
|
|
91
|
+
.data tbody:not(.no-anim) tr:nth-child(6) { animation-delay: 100ms; }
|
|
92
|
+
.data tbody:not(.no-anim) tr:nth-child(7) { animation-delay: 120ms; }
|
|
93
|
+
.data tbody:not(.no-anim) tr:nth-child(8) { animation-delay: 140ms; }
|
|
94
|
+
.data tbody:not(.no-anim) tr:nth-child(n+9) { animation-delay: 160ms; }
|
|
95
|
+
@keyframes row-in {
|
|
96
|
+
from { opacity: 0; transform: translateY(3px); }
|
|
97
|
+
to { opacity: 1; transform: translateY(0); }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Status indicator dot — claude-like. Base color is the muted gray so any
|
|
101
|
+
unrecognized status string Claude writes ("compacting", "thinking", a
|
|
102
|
+
transient state, etc.) falls back to "unknown-looking" rather than a
|
|
103
|
+
stark black dot. .busy / .idle override. */
|
|
104
|
+
.status-mark {
|
|
105
|
+
display: inline-block;
|
|
106
|
+
width: 8px;
|
|
107
|
+
height: 8px;
|
|
108
|
+
border-radius: 50%;
|
|
109
|
+
background: currentColor;
|
|
110
|
+
vertical-align: middle;
|
|
111
|
+
position: relative;
|
|
112
|
+
color: var(--ink-faint);
|
|
113
|
+
}
|
|
114
|
+
.status-mark.busy {
|
|
115
|
+
color: var(--blue);
|
|
116
|
+
box-shadow: 0 0 0 0 currentColor;
|
|
117
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
118
|
+
}
|
|
119
|
+
.status-mark.idle { color: var(--green); }
|
|
120
|
+
.status-mark.unknown { color: var(--ink-faint); }
|
|
121
|
+
@keyframes pulse {
|
|
122
|
+
0% { box-shadow: 0 0 0 0 rgba(74, 115, 165, 0.4); }
|
|
123
|
+
70% { box-shadow: 0 0 0 7px rgba(74, 115, 165, 0); }
|
|
124
|
+
100% { box-shadow: 0 0 0 0 rgba(74, 115, 165, 0); }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Title and path are fixed-width with overflow ellipsis on the inner
|
|
128
|
+
spans so the sticky actions column is always visible at typical widths;
|
|
129
|
+
long values truncate, the title attr shows the full value on hover. */
|
|
130
|
+
.title-cell {
|
|
131
|
+
width: 300px;
|
|
132
|
+
max-width: 300px;
|
|
133
|
+
min-width: 0;
|
|
134
|
+
}
|
|
135
|
+
.title-cell .title-row {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
gap: 6px;
|
|
139
|
+
min-width: 0;
|
|
140
|
+
width: 100%;
|
|
141
|
+
}
|
|
142
|
+
.title-cell .primary {
|
|
143
|
+
color: var(--ink);
|
|
144
|
+
font-weight: 500;
|
|
145
|
+
white-space: nowrap;
|
|
146
|
+
overflow: hidden;
|
|
147
|
+
text-overflow: ellipsis;
|
|
148
|
+
font-size: 13.5px;
|
|
149
|
+
min-width: 0;
|
|
150
|
+
flex: 0 1 auto;
|
|
151
|
+
}
|
|
152
|
+
.title-cell .secondary {
|
|
153
|
+
font-family: var(--mono);
|
|
154
|
+
font-size: 10.5px;
|
|
155
|
+
color: var(--ink-muted);
|
|
156
|
+
letter-spacing: 0.02em;
|
|
157
|
+
margin-top: 2px;
|
|
158
|
+
white-space: nowrap;
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
text-overflow: ellipsis;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.path-cell {
|
|
164
|
+
font-family: var(--mono);
|
|
165
|
+
font-size: 11.5px;
|
|
166
|
+
color: var(--ink-mid);
|
|
167
|
+
width: 260px;
|
|
168
|
+
max-width: 260px;
|
|
169
|
+
min-width: 0;
|
|
170
|
+
white-space: nowrap;
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
text-overflow: ellipsis;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.branch-tag {
|
|
176
|
+
display: inline-block;
|
|
177
|
+
font-family: var(--mono);
|
|
178
|
+
font-size: 11px;
|
|
179
|
+
color: var(--ink-mid);
|
|
180
|
+
background: var(--bg);
|
|
181
|
+
padding: 2px 7px;
|
|
182
|
+
border-radius: 4px;
|
|
183
|
+
border: 1px solid var(--border-soft);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.row-actions {
|
|
187
|
+
display: flex;
|
|
188
|
+
gap: var(--s-2);
|
|
189
|
+
justify-content: flex-end;
|
|
190
|
+
}
|
|
191
|
+
/* Equalised width so Focus / Continue / Resume align cleanly down the
|
|
192
|
+
actions column (last column is sticky, so width drift is visible).
|
|
193
|
+
.action is inline-flex with justify-content:center, so the icon+text
|
|
194
|
+
cluster sits in the middle. */
|
|
195
|
+
.row-actions .action.small {
|
|
196
|
+
min-width: 96px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Star toggle — inline next to the title text. Outline by default;
|
|
200
|
+
filled in ink when pinned. */
|
|
201
|
+
.star-btn {
|
|
202
|
+
appearance: none;
|
|
203
|
+
background: transparent;
|
|
204
|
+
border: 0;
|
|
205
|
+
padding: 2px;
|
|
206
|
+
margin: 0;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
color: var(--ink-muted);
|
|
209
|
+
display: inline-flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
border-radius: 4px;
|
|
213
|
+
transition: color .12s ease, background .12s ease, transform .15s ease;
|
|
214
|
+
line-height: 0;
|
|
215
|
+
flex: 0 0 auto;
|
|
216
|
+
}
|
|
217
|
+
.data tbody tr:hover .star-btn { color: var(--ink-mid); }
|
|
218
|
+
.star-btn:hover {
|
|
219
|
+
color: #e3b341 !important;
|
|
220
|
+
background: var(--bg);
|
|
221
|
+
}
|
|
222
|
+
.star-btn:active { transform: scale(0.88); }
|
|
223
|
+
.star-btn.is-fav { color: #e3b341; }
|
|
224
|
+
.star-btn svg { display: block; }
|
|
225
|
+
|
|
226
|
+
/* Rename (pencil) — only visible on row hover so it doesn't compete with
|
|
227
|
+
the star. When a label is set (.has-label), it stays subtly visible to
|
|
228
|
+
indicate the title is overridden. */
|
|
229
|
+
.rename-btn {
|
|
230
|
+
appearance: none;
|
|
231
|
+
background: transparent;
|
|
232
|
+
border: 0;
|
|
233
|
+
padding: 2px;
|
|
234
|
+
margin: 0;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
color: var(--ink-faint);
|
|
237
|
+
opacity: 0;
|
|
238
|
+
display: inline-flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
justify-content: center;
|
|
241
|
+
border-radius: 4px;
|
|
242
|
+
transition: opacity .12s ease, color .12s ease, background .12s ease, transform .15s ease;
|
|
243
|
+
line-height: 0;
|
|
244
|
+
flex: 0 0 auto;
|
|
245
|
+
}
|
|
246
|
+
.data tbody tr:hover .rename-btn {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
color: var(--ink-muted);
|
|
249
|
+
}
|
|
250
|
+
.rename-btn:hover {
|
|
251
|
+
color: var(--ink) !important;
|
|
252
|
+
background: var(--bg);
|
|
253
|
+
}
|
|
254
|
+
.rename-btn:active { transform: scale(0.88); }
|
|
255
|
+
.rename-btn.has-label {
|
|
256
|
+
opacity: 0.8;
|
|
257
|
+
color: var(--ink);
|
|
258
|
+
}
|
|
259
|
+
.rename-btn svg { display: block; }
|
|
260
|
+
|
|
261
|
+
#favoritesEmpty {
|
|
262
|
+
padding: var(--s-6) var(--s-6);
|
|
263
|
+
text-align: center;
|
|
264
|
+
font-size: 12.5px;
|
|
265
|
+
color: var(--ink-muted);
|
|
266
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Terminals tab · left rail (active sessions) + right pane (xterm host) */
|
|
2
|
+
|
|
3
|
+
.terminals-layout {
|
|
4
|
+
display: grid;
|
|
5
|
+
grid-template-columns: 240px 1fr;
|
|
6
|
+
gap: var(--s-4);
|
|
7
|
+
/* viewport minus page header + footer + padding; lets xterm fill height */
|
|
8
|
+
height: calc(100vh - 220px);
|
|
9
|
+
min-height: 480px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.terminals-rail {
|
|
13
|
+
background: var(--bg-elev);
|
|
14
|
+
border: 1px solid var(--border);
|
|
15
|
+
border-radius: var(--r-md);
|
|
16
|
+
padding: var(--s-2);
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 2px;
|
|
20
|
+
overflow-y: auto;
|
|
21
|
+
}
|
|
22
|
+
.terminals-rail-head {
|
|
23
|
+
display: flex;
|
|
24
|
+
justify-content: space-between;
|
|
25
|
+
align-items: center;
|
|
26
|
+
padding: var(--s-2) var(--s-3);
|
|
27
|
+
font-size: 11px;
|
|
28
|
+
text-transform: uppercase;
|
|
29
|
+
letter-spacing: 0.06em;
|
|
30
|
+
color: var(--ink-muted);
|
|
31
|
+
border-bottom: 1px solid var(--border-soft);
|
|
32
|
+
margin-bottom: var(--s-2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* button row showing one terminal in the rail */
|
|
36
|
+
.terminal-row {
|
|
37
|
+
appearance: none;
|
|
38
|
+
background: transparent;
|
|
39
|
+
border: 0;
|
|
40
|
+
width: 100%;
|
|
41
|
+
text-align: left;
|
|
42
|
+
padding: 8px 10px;
|
|
43
|
+
border-radius: var(--r-sm);
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
display: grid;
|
|
46
|
+
grid-template-columns: 10px 1fr auto auto;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 8px;
|
|
49
|
+
color: var(--ink-mid);
|
|
50
|
+
font-family: var(--body);
|
|
51
|
+
font-size: 13px;
|
|
52
|
+
transition: background .12s ease, color .12s ease;
|
|
53
|
+
}
|
|
54
|
+
.terminal-row:hover { background: var(--bg); color: var(--ink); }
|
|
55
|
+
.terminal-row.is-active {
|
|
56
|
+
background: var(--sidebar-active);
|
|
57
|
+
color: var(--ink);
|
|
58
|
+
}
|
|
59
|
+
.terminal-row-title {
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
text-overflow: ellipsis;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
}
|
|
66
|
+
.terminal-row-meta {
|
|
67
|
+
font-family: var(--mono);
|
|
68
|
+
font-size: 10.5px;
|
|
69
|
+
color: var(--ink-muted);
|
|
70
|
+
font-variant-numeric: tabular-nums;
|
|
71
|
+
}
|
|
72
|
+
.terminal-row-actions { display: inline-flex; }
|
|
73
|
+
.terminal-row .action.tiny.danger {
|
|
74
|
+
padding: 1px 7px;
|
|
75
|
+
font-size: 11px;
|
|
76
|
+
min-width: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* right pane — full-height xterm host */
|
|
80
|
+
.terminals-main {
|
|
81
|
+
background: var(--bg);
|
|
82
|
+
border: 1px solid var(--border);
|
|
83
|
+
border-radius: var(--r-md);
|
|
84
|
+
padding: var(--s-3);
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
}
|
|
90
|
+
.terminal-host {
|
|
91
|
+
flex: 1;
|
|
92
|
+
min-height: 0;
|
|
93
|
+
width: 100%;
|
|
94
|
+
}
|
|
95
|
+
/* Don't override xterm's background — its renderer (canvas/WebGL) assumes
|
|
96
|
+
an opaque surface and ghosts on scroll if we force transparent. The
|
|
97
|
+
theme object in TerminalView.js already paints #faf9f5 to match the
|
|
98
|
+
surrounding card. */
|
|
99
|
+
.terminal-host .xterm-viewport {
|
|
100
|
+
scrollbar-width: thin;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.terminal-empty {
|
|
104
|
+
height: 100%;
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
font-size: 13px;
|
|
109
|
+
color: var(--ink-muted);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.terminal-empty-page { width: 100%; }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* ccsm · design tokens · v0.6 */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Surfaces — cream with neutral whites. Sidebar shares the page bg
|
|
5
|
+
so they read as one continuous surface; separation is just the
|
|
6
|
+
hairline border on .sidebar. */
|
|
7
|
+
--bg: #faf9f5;
|
|
8
|
+
--bg-elev: #ffffff;
|
|
9
|
+
--sidebar-bg: #faf9f5;
|
|
10
|
+
--sidebar-hover: #f0ece0;
|
|
11
|
+
--sidebar-active: #e8e3d5;
|
|
12
|
+
|
|
13
|
+
/* Borders & rules */
|
|
14
|
+
--border: #e8e3d5;
|
|
15
|
+
--border-soft: #ece8da;
|
|
16
|
+
--border-strong: #d4cdb8;
|
|
17
|
+
|
|
18
|
+
/* Ink */
|
|
19
|
+
--ink: #1a1815;
|
|
20
|
+
--ink-mid: #534e44;
|
|
21
|
+
--ink-muted: #8a8475;
|
|
22
|
+
--ink-faint: #b5af9d;
|
|
23
|
+
|
|
24
|
+
/* Accent — Claude warm copper. Slightly desaturated from raw #c45f3f so
|
|
25
|
+
solid-fill buttons feel calm against the cream surfaces, while still
|
|
26
|
+
reading as the same brand hue in star icons, focus rings, etc. */
|
|
27
|
+
--accent: #b3614a;
|
|
28
|
+
--accent-deep: #8f4a36;
|
|
29
|
+
--accent-soft: rgba(179, 97, 74, 0.10);
|
|
30
|
+
--accent-softer: rgba(179, 97, 74, 0.04);
|
|
31
|
+
|
|
32
|
+
/* Status */
|
|
33
|
+
--green: #4a8a4a;
|
|
34
|
+
--yellow: #c4892b;
|
|
35
|
+
--red: #b73f3f;
|
|
36
|
+
--blue: #4a73a5;
|
|
37
|
+
|
|
38
|
+
/* Type */
|
|
39
|
+
--body: "Geist", -apple-system, "Segoe UI", system-ui, sans-serif;
|
|
40
|
+
--mono: "JetBrains Mono", "Cascadia Mono", "Consolas", monospace;
|
|
41
|
+
|
|
42
|
+
/* Scale */
|
|
43
|
+
--s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
|
|
44
|
+
--s-5: 20px; --s-6: 24px; --s-8: 32px; --s-10: 40px;
|
|
45
|
+
--s-12: 48px; --s-16: 64px;
|
|
46
|
+
|
|
47
|
+
/* Radius */
|
|
48
|
+
--r-sm: 6px;
|
|
49
|
+
--r: 8px;
|
|
50
|
+
--r-md: 10px;
|
|
51
|
+
--r-lg: 14px;
|
|
52
|
+
|
|
53
|
+
/* Shadow — soft, like print on cream */
|
|
54
|
+
--shadow-sm: 0 1px 0 rgba(26, 24, 21, 0.04);
|
|
55
|
+
--shadow: 0 1px 2px rgba(26, 24, 21, 0.04),
|
|
56
|
+
0 1px 0 rgba(26, 24, 21, 0.03);
|
|
57
|
+
--shadow-md: 0 4px 12px -2px rgba(26, 24, 21, 0.08),
|
|
58
|
+
0 1px 0 rgba(26, 24, 21, 0.04);
|
|
59
|
+
|
|
60
|
+
/* Sidebar geometry */
|
|
61
|
+
--sidebar-w: 232px;
|
|
62
|
+
--sidebar-w-collapsed: 60px;
|
|
63
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* PWA / --app= window drag regions.
|
|
2
|
+
*
|
|
3
|
+
* `-webkit-app-region: drag` is a Chromium-only property; it has effect in
|
|
4
|
+
* Electron, installed PWA windows, AND in `--app=` chromeless mode where
|
|
5
|
+
* the manifest's display_override unlocks WCO-ish behaviour. In a plain
|
|
6
|
+
* browser tab it's silently ignored. We therefore apply it *unconditionally*
|
|
7
|
+
* (no `@media (display-mode: …)` gate) so any surface that does honour it
|
|
8
|
+
* gets the drag handles — particularly the case where Edge `--app=` reads
|
|
9
|
+
* our manifest, hides its own OS title bar, but still reports
|
|
10
|
+
* display-mode: browser. Without unconditional rules, that window has
|
|
11
|
+
* neither a native title bar to grab NOR app-region drag → undraggable.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
.sidebar-brand,
|
|
15
|
+
.page-head,
|
|
16
|
+
.page-head-inner,
|
|
17
|
+
.page-title,
|
|
18
|
+
.page-subtitle {
|
|
19
|
+
-webkit-app-region: drag;
|
|
20
|
+
app-region: drag;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Any interactive element opts out of drag so click / focus / scroll
|
|
24
|
+
still reach the page. */
|
|
25
|
+
button, a, input, select, textarea, label,
|
|
26
|
+
.nav-item, .util-item, .collapse-toggle,
|
|
27
|
+
.action, .server-status,
|
|
28
|
+
.star-btn, .rename-btn, .card-fold, .modal-close,
|
|
29
|
+
.chip, .radio, .fab,
|
|
30
|
+
.ph-stat, .ph-divider {
|
|
31
|
+
-webkit-app-region: no-drag;
|
|
32
|
+
app-region: no-drag;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* `user-select: none` on the drag-target text is needed so clicking the
|
|
36
|
+
page title doesn't start a text selection instead of a window drag. But
|
|
37
|
+
we only want this in PWA-like contexts — in a regular browser tab the
|
|
38
|
+
user should still be able to select the title text. Runtime JS adds
|
|
39
|
+
`body.is-app` when not in display-mode:browser; see main.js. */
|
|
40
|
+
body.is-app .sidebar-brand,
|
|
41
|
+
body.is-app .page-head,
|
|
42
|
+
body.is-app .page-head-inner,
|
|
43
|
+
body.is-app .page-title,
|
|
44
|
+
body.is-app .page-subtitle {
|
|
45
|
+
user-select: none;
|
|
46
|
+
-webkit-user-select: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* In an app window, .main and .sidebar's own top padding is "blank space"
|
|
50
|
+
that has no drag region — clicking the topmost 32px does nothing. Move
|
|
51
|
+
that padding into the draggable child elements (.sidebar-brand and
|
|
52
|
+
.page-head) so the windowtop is grabbable all the way to y=0. Visually
|
|
53
|
+
nothing shifts; behaviourally the OS-title-bar zone now responds. */
|
|
54
|
+
body.is-app .sidebar { padding-top: 0; }
|
|
55
|
+
body.is-app .main { padding-top: 0; }
|
|
56
|
+
body.is-app .sidebar-brand {
|
|
57
|
+
padding-top: calc(var(--s-4) + var(--s-4)); /* 32px = old sidebar.pt + old brand.pt */
|
|
58
|
+
}
|
|
59
|
+
body.is-app .page-head {
|
|
60
|
+
padding-top: var(--s-8); /* 32px = old main.pt */
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* WCO-only · the OS title bar is fully gone, so right-pad the page-head
|
|
64
|
+
so the meta cluster doesn't slide under the floating close/max/min
|
|
65
|
+
controls. `.main`'s own 32px top padding already clears their height. */
|
|
66
|
+
@media (display-mode: window-controls-overlay) {
|
|
67
|
+
.page-head {
|
|
68
|
+
padding-right: calc(var(--s-10) + 100vw - env(titlebar-area-width, 100vw));
|
|
69
|
+
}
|
|
70
|
+
}
|