@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.
Files changed (67) hide show
  1. package/README.md +172 -38
  2. package/bin/ccsm.js +194 -0
  3. package/lib/favorites.js +23 -45
  4. package/lib/jsonStore.js +60 -0
  5. package/lib/labels.js +21 -41
  6. package/lib/webTerminal.js +173 -0
  7. package/package.json +11 -3
  8. package/public/css/base.css +82 -0
  9. package/public/css/cards.css +149 -0
  10. package/public/css/feedback.css +219 -0
  11. package/public/css/forms.css +282 -0
  12. package/public/css/layout.css +107 -0
  13. package/public/css/modal.css +169 -0
  14. package/public/css/responsive.css +10 -0
  15. package/public/css/sidebar.css +165 -0
  16. package/public/css/tables.css +266 -0
  17. package/public/css/terminals.css +112 -0
  18. package/public/css/tokens.css +63 -0
  19. package/public/css/wco.css +70 -0
  20. package/public/css/widgets.css +204 -0
  21. package/public/favicon.svg +1 -1
  22. package/public/index.html +52 -490
  23. package/public/js/actions.js +87 -0
  24. package/public/js/api.js +103 -0
  25. package/public/js/backend.js +28 -0
  26. package/public/js/components/App.js +45 -0
  27. package/public/js/components/Card.js +24 -0
  28. package/public/js/components/DialogHost.js +45 -0
  29. package/public/js/components/Fab.js +11 -0
  30. package/public/js/components/FavoritesTable.js +81 -0
  31. package/public/js/components/Footer.js +12 -0
  32. package/public/js/components/NewSessionModal.js +142 -0
  33. package/public/js/components/OfflineBanner.js +52 -0
  34. package/public/js/components/PageHead.js +33 -0
  35. package/public/js/components/Pagination.js +27 -0
  36. package/public/js/components/ProgressList.js +32 -0
  37. package/public/js/components/RecentTable.js +68 -0
  38. package/public/js/components/RepoPicker.js +40 -0
  39. package/public/js/components/ReposEditor.js +74 -0
  40. package/public/js/components/ServerStatus.js +18 -0
  41. package/public/js/components/SessionsTable.js +71 -0
  42. package/public/js/components/Sidebar.js +52 -0
  43. package/public/js/components/SnapshotPanel.js +77 -0
  44. package/public/js/components/TerminalView.js +108 -0
  45. package/public/js/components/TitleCell.js +40 -0
  46. package/public/js/components/Toast.js +8 -0
  47. package/public/js/components/WorkspacePicker.js +19 -0
  48. package/public/js/components/WorkspacesGrid.js +41 -0
  49. package/public/js/dialog.js +59 -0
  50. package/public/js/html.js +6 -0
  51. package/public/js/icons.js +114 -0
  52. package/public/js/main.js +81 -0
  53. package/public/js/pages/AboutPage.js +85 -0
  54. package/public/js/pages/ConfigurePage.js +194 -0
  55. package/public/js/pages/LaunchPage.js +117 -0
  56. package/public/js/pages/SessionsPage.js +47 -0
  57. package/public/js/pages/TerminalsPage.js +74 -0
  58. package/public/js/state.js +87 -0
  59. package/public/js/streaming.js +96 -0
  60. package/public/js/toast.js +14 -0
  61. package/public/js/util.js +24 -0
  62. package/public/manifest.webmanifest +14 -0
  63. package/scripts/install.js +111 -0
  64. package/scripts/uninstall.js +56 -0
  65. package/server.js +286 -30
  66. package/public/app.js +0 -1353
  67. package/public/styles.css +0 -1639
@@ -0,0 +1,282 @@
1
+ /* Buttons (.action variants) · inputs · checkbox · chip pills ·
2
+ form-row layout · config grid · repos sub-table */
3
+
4
+ .action {
5
+ appearance: none;
6
+ background: var(--bg-elev);
7
+ border: 1px solid var(--border-strong);
8
+ color: var(--ink);
9
+ padding: 7px 14px;
10
+ font-family: var(--body);
11
+ font-size: 13px;
12
+ font-weight: 500;
13
+ letter-spacing: -0.005em;
14
+ border-radius: var(--r-sm);
15
+ cursor: pointer;
16
+ transition: background .12s ease, border-color .12s ease, color .12s ease, box-shadow .12s ease;
17
+ white-space: nowrap;
18
+ box-shadow: var(--shadow-sm);
19
+ /* Buttons always lay out as icon-left + text-right, gap between them,
20
+ and the cluster is centered inside the button's box. Anchor links
21
+ using .action (e.g. About page) inherit the same layout. */
22
+ display: inline-flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ gap: 6px;
26
+ text-decoration: none;
27
+ }
28
+ .action:hover {
29
+ background: var(--bg);
30
+ border-color: var(--ink-faint);
31
+ }
32
+ .action:active { transform: translateY(0.5px); }
33
+ .action:disabled { opacity: .5; cursor: not-allowed; pointer-events: none; }
34
+ .action.primary {
35
+ background: var(--ink);
36
+ border-color: var(--ink);
37
+ color: var(--bg-elev);
38
+ }
39
+ .action.primary:hover {
40
+ background: #000;
41
+ border-color: #000;
42
+ }
43
+ .action.small { font-size: 12px; padding: 4px 10px; }
44
+ .action.tiny { font-size: 11px; padding: 3px 8px; }
45
+ .action.subtle {
46
+ background: transparent;
47
+ border-color: var(--border);
48
+ box-shadow: none;
49
+ color: var(--ink-mid);
50
+ }
51
+ .action.subtle:hover { background: var(--bg); color: var(--ink); }
52
+ .action.danger {
53
+ background: var(--red);
54
+ border-color: var(--red);
55
+ color: var(--bg-elev);
56
+ }
57
+ .action.danger:hover {
58
+ background: #9a3636;
59
+ border-color: #9a3636;
60
+ }
61
+
62
+ .input, input[type="text"], input[type="number"], select, textarea {
63
+ appearance: none;
64
+ background: var(--bg-elev);
65
+ border: 1px solid var(--border-strong);
66
+ color: var(--ink);
67
+ padding: 7px 12px;
68
+ font-family: var(--body);
69
+ font-size: 13px;
70
+ border-radius: var(--r-sm);
71
+ transition: border-color .12s ease, box-shadow .12s ease;
72
+ width: 100%;
73
+ max-width: 480px;
74
+ }
75
+ .input.narrow { min-width: 240px; max-width: 320px; }
76
+ .input:focus, input:focus, select:focus, textarea:focus {
77
+ outline: none;
78
+ border-color: var(--ink);
79
+ box-shadow: 0 0 0 3px rgba(26, 24, 21, 0.08);
80
+ }
81
+ select {
82
+ background-image: url("data:image/svg+xml;utf8,<svg viewBox='0 0 12 8' xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%238a8475' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='1,1 6,7 11,1'/></svg>");
83
+ background-repeat: no-repeat;
84
+ background-position: right 10px center;
85
+ background-size: 10px;
86
+ padding-right: 28px;
87
+ }
88
+ textarea {
89
+ font-family: var(--mono);
90
+ font-size: 12px;
91
+ resize: vertical;
92
+ line-height: 1.55;
93
+ }
94
+
95
+ input[type="checkbox"] {
96
+ appearance: none;
97
+ width: 16px;
98
+ height: 16px;
99
+ flex: 0 0 16px;
100
+ border: 1px solid var(--border-strong);
101
+ background: var(--bg-elev);
102
+ border-radius: 4px;
103
+ cursor: pointer;
104
+ position: relative;
105
+ transition: background .12s, border-color .12s;
106
+ }
107
+ input[type="checkbox"]:checked {
108
+ background: var(--ink);
109
+ border-color: var(--ink);
110
+ }
111
+ input[type="checkbox"]:checked::after {
112
+ content: "";
113
+ position: absolute;
114
+ left: 4px; top: 1px;
115
+ width: 5px; height: 9px;
116
+ border: solid var(--bg-elev);
117
+ border-width: 0 1.5px 1.5px 0;
118
+ transform: rotate(45deg);
119
+ }
120
+
121
+ .form-row {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: var(--s-4);
125
+ margin-bottom: var(--s-3);
126
+ flex-wrap: wrap;
127
+ }
128
+ .form-row:last-child { margin-bottom: 0; }
129
+ .form-label {
130
+ font-family: var(--body);
131
+ font-size: 12px;
132
+ font-weight: 500;
133
+ color: var(--ink-mid);
134
+ min-width: 96px;
135
+ }
136
+ .form-actions {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: var(--s-3);
140
+ margin-top: var(--s-4);
141
+ }
142
+
143
+ /* Radio pills — same visual family as chip but exclusive (radio not checkbox) */
144
+ .radio-row {
145
+ display: flex;
146
+ gap: var(--s-2);
147
+ flex-wrap: wrap;
148
+ }
149
+ .radio {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ gap: 6px;
153
+ padding: 5px 12px;
154
+ border: 1px solid var(--border-strong);
155
+ background: var(--bg-elev);
156
+ color: var(--ink-mid);
157
+ font-family: var(--body);
158
+ font-size: 12.5px;
159
+ font-weight: 500;
160
+ cursor: pointer;
161
+ user-select: none;
162
+ transition: all .12s ease;
163
+ border-radius: 999px;
164
+ }
165
+ .radio input { display: none; }
166
+ .radio:hover { color: var(--ink); border-color: var(--ink-faint); }
167
+ .radio::before {
168
+ content: "";
169
+ width: 7px;
170
+ height: 7px;
171
+ border: 1px solid var(--ink-faint);
172
+ border-radius: 50%;
173
+ background: transparent;
174
+ }
175
+ .radio.is-checked {
176
+ background: var(--bg);
177
+ border-color: var(--ink);
178
+ color: var(--ink);
179
+ }
180
+ .radio.is-checked::before {
181
+ background: var(--ink);
182
+ border-color: var(--ink);
183
+ box-shadow: inset 0 0 0 2px var(--bg-elev);
184
+ }
185
+
186
+ /* Chip-style multi-select used for repo picker etc. */
187
+ .chip-row {
188
+ display: flex;
189
+ flex-wrap: wrap;
190
+ gap: var(--s-2);
191
+ flex: 1;
192
+ }
193
+ .chip {
194
+ display: inline-flex;
195
+ align-items: center;
196
+ gap: 6px;
197
+ padding: 5px 12px;
198
+ border: 1px solid var(--border-strong);
199
+ background: var(--bg-elev);
200
+ color: var(--ink-mid);
201
+ font-family: var(--body);
202
+ font-size: 12.5px;
203
+ font-weight: 500;
204
+ cursor: pointer;
205
+ user-select: none;
206
+ transition: all .12s ease;
207
+ border-radius: 999px;
208
+ }
209
+ .chip input { display: none; }
210
+ .chip:hover {
211
+ color: var(--ink);
212
+ border-color: var(--ink-faint);
213
+ }
214
+ .chip::before {
215
+ content: "";
216
+ width: 7px;
217
+ height: 7px;
218
+ border: 1px solid var(--ink-faint);
219
+ border-radius: 50%;
220
+ background: transparent;
221
+ }
222
+ .chip.checked {
223
+ background: var(--bg);
224
+ border-color: var(--ink);
225
+ color: var(--ink);
226
+ }
227
+ .chip.checked::before {
228
+ background: var(--ink);
229
+ border-color: var(--ink);
230
+ box-shadow: inset 0 0 0 2px var(--bg-elev);
231
+ }
232
+
233
+ /* Two-column field grid used inside the Configure card */
234
+ .config-grid {
235
+ display: grid;
236
+ grid-template-columns: 1fr 1fr;
237
+ gap: var(--s-5) var(--s-6);
238
+ align-items: start;
239
+ }
240
+ .config-grid .field { display: flex; flex-direction: column; gap: 6px; }
241
+ .config-grid .field.full { grid-column: 1 / -1; }
242
+ .config-grid .field .label {
243
+ font-size: 12px;
244
+ font-weight: 500;
245
+ color: var(--ink-mid);
246
+ }
247
+ .config-grid .field .hint {
248
+ font-size: 11.5px;
249
+ color: var(--ink-muted);
250
+ font-style: italic;
251
+ }
252
+ .config-grid .field .hint.inline {
253
+ display: inline;
254
+ margin-left: 4px;
255
+ font-style: italic;
256
+ }
257
+ .config-grid .field.toggle {
258
+ flex-direction: row;
259
+ align-items: flex-start;
260
+ gap: var(--s-3);
261
+ padding-top: var(--s-4);
262
+ }
263
+ .config-grid .field.toggle .toggle-text {
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: 2px;
267
+ }
268
+ .config-grid .field.toggle .label { font-weight: 500; }
269
+
270
+ /* Inline editable repo list (Configure tab) */
271
+ .repos-head {
272
+ display: flex;
273
+ justify-content: space-between;
274
+ align-items: center;
275
+ margin-bottom: var(--s-2);
276
+ }
277
+ .repos-table thead th, .repos-table tbody td { padding: 8px var(--s-2); }
278
+ .repos-table thead th:first-child,
279
+ .repos-table tbody td:first-child { padding-left: var(--s-2); }
280
+ .repos-table thead th:last-child,
281
+ .repos-table tbody td:last-child { padding-right: var(--s-2); }
282
+ .repos-table tbody td input { font-size: 12px; max-width: none; }
@@ -0,0 +1,107 @@
1
+ /* App grid · main column · page header · tab panels · footer status line */
2
+
3
+ .app {
4
+ display: grid;
5
+ grid-template-columns: var(--sidebar-w) 1fr;
6
+ min-height: 100vh;
7
+ transition: grid-template-columns .25s cubic-bezier(.4, 0, .2, 1);
8
+ }
9
+ .app:has(.sidebar[data-collapsed="true"]) {
10
+ grid-template-columns: var(--sidebar-w-collapsed) 1fr;
11
+ }
12
+
13
+ .main {
14
+ display: flex;
15
+ flex-direction: column;
16
+ min-width: 0;
17
+ padding: var(--s-8) var(--s-10) var(--s-6);
18
+ gap: var(--s-6);
19
+ }
20
+
21
+ .page-head {
22
+ display: flex;
23
+ align-items: flex-start;
24
+ justify-content: space-between;
25
+ gap: var(--s-8);
26
+ padding-bottom: var(--s-5);
27
+ border-bottom: 1px solid var(--border);
28
+ }
29
+ .page-head-inner { min-width: 0; }
30
+
31
+ .page-title {
32
+ font-size: 26px;
33
+ font-weight: 600;
34
+ letter-spacing: -0.024em;
35
+ color: var(--ink);
36
+ line-height: 1.1;
37
+ }
38
+ .page-subtitle {
39
+ margin-top: 4px;
40
+ font-size: 13.5px;
41
+ color: var(--ink-mid);
42
+ }
43
+
44
+ .page-head-meta {
45
+ display: flex;
46
+ align-items: baseline;
47
+ gap: var(--s-3);
48
+ flex-shrink: 0;
49
+ white-space: nowrap;
50
+ font-family: var(--mono);
51
+ font-size: 11px;
52
+ color: var(--ink-muted);
53
+ }
54
+ .ph-stat { display: inline-flex; gap: 6px; align-items: baseline; }
55
+ .ph-key {
56
+ font-family: var(--body);
57
+ font-size: 10.5px;
58
+ letter-spacing: 0.06em;
59
+ text-transform: uppercase;
60
+ color: var(--ink-faint);
61
+ }
62
+ .ph-val { color: var(--ink-mid); }
63
+ .ph-divider { color: var(--ink-faint); }
64
+
65
+ .content {
66
+ flex: 1;
67
+ display: flex;
68
+ flex-direction: column;
69
+ }
70
+
71
+ .tab-panel {
72
+ display: none;
73
+ flex-direction: column;
74
+ gap: var(--s-6);
75
+ }
76
+ .tab-panel[data-active] {
77
+ display: flex;
78
+ animation: panel-in .35s cubic-bezier(.4, 0, .2, 1);
79
+ }
80
+ @keyframes panel-in {
81
+ from { opacity: 0; transform: translateY(6px); }
82
+ to { opacity: 1; transform: translateY(0); }
83
+ }
84
+
85
+ .footer-status {
86
+ margin-top: auto;
87
+ padding-top: var(--s-4);
88
+ border-top: 1px solid var(--border-soft);
89
+ display: flex;
90
+ flex-wrap: wrap;
91
+ align-items: baseline;
92
+ gap: var(--s-2);
93
+ font-size: 11px;
94
+ color: var(--ink-muted);
95
+ }
96
+ .footer-status .fs-key {
97
+ text-transform: uppercase;
98
+ letter-spacing: 0.08em;
99
+ font-size: 10px;
100
+ color: var(--ink-faint);
101
+ }
102
+ .footer-status .fs-val {
103
+ font-family: var(--mono);
104
+ font-size: 11px;
105
+ color: var(--ink-mid);
106
+ }
107
+ .footer-status .fs-divider { color: var(--ink-faint); margin: 0 var(--s-1); }
@@ -0,0 +1,169 @@
1
+ /* Floating action button · modal backdrop / shell / sections ·
2
+ nested repos-inline-config disclosure · ad-hoc confirm/prompt dialog */
3
+
4
+ .fab {
5
+ position: fixed;
6
+ bottom: var(--s-6);
7
+ right: var(--s-6);
8
+ z-index: 90;
9
+ width: 48px;
10
+ height: 48px;
11
+ border-radius: 50%;
12
+ background: var(--ink);
13
+ color: var(--bg-elev);
14
+ border: 0;
15
+ cursor: pointer;
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ box-shadow:
20
+ 0 6px 16px -6px rgba(26, 24, 21, 0.25),
21
+ 0 1px 0 rgba(26, 24, 21, 0.06);
22
+ transition: background .12s ease, transform .15s ease, box-shadow .15s ease;
23
+ }
24
+ .fab:hover {
25
+ background: #000;
26
+ transform: translateY(-1px);
27
+ box-shadow:
28
+ 0 10px 22px -6px rgba(26, 24, 21, 0.26),
29
+ 0 2px 0 rgba(26, 24, 21, 0.08);
30
+ }
31
+ .fab:active { transform: scale(0.96); }
32
+
33
+ .modal-backdrop {
34
+ position: fixed;
35
+ inset: 0;
36
+ z-index: 200;
37
+ background: rgba(26, 24, 21, 0.48);
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ padding: var(--s-6);
42
+ animation: backdrop-in .18s ease;
43
+ }
44
+ @keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }
45
+
46
+ .modal {
47
+ background: var(--bg-elev);
48
+ border: 1px solid var(--border);
49
+ border-radius: var(--r-md);
50
+ width: min(560px, 100%);
51
+ max-height: 90vh;
52
+ display: flex;
53
+ flex-direction: column;
54
+ overflow: hidden;
55
+ 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
+ animation: modal-in .22s cubic-bezier(.4, 0, .2, 1);
59
+ }
60
+ @keyframes modal-in {
61
+ from { opacity: 0; transform: translateY(12px) scale(0.98); }
62
+ to { opacity: 1; transform: translateY(0) scale(1); }
63
+ }
64
+
65
+ .modal-head {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: space-between;
69
+ padding: var(--s-4) var(--s-6);
70
+ border-bottom: 1px solid var(--border-soft);
71
+ }
72
+ .modal-head h2 {
73
+ font-size: 16px;
74
+ font-weight: 600;
75
+ color: var(--ink);
76
+ }
77
+ .modal-close {
78
+ appearance: none;
79
+ background: transparent;
80
+ border: 0;
81
+ padding: 4px;
82
+ margin: -4px;
83
+ cursor: pointer;
84
+ color: var(--ink-muted);
85
+ border-radius: 4px;
86
+ display: inline-flex;
87
+ transition: color .12s ease, background .12s ease;
88
+ }
89
+ .modal-close:hover { color: var(--ink); background: var(--bg); }
90
+
91
+ .modal-body {
92
+ padding: var(--s-5) var(--s-6);
93
+ overflow-y: auto;
94
+ flex: 1;
95
+ }
96
+ .modal-hint {
97
+ font-size: 13px;
98
+ color: var(--ink-muted);
99
+ margin-bottom: var(--s-4);
100
+ }
101
+ .modal-hint code {
102
+ font-family: var(--mono);
103
+ font-size: 11.5px;
104
+ background: var(--bg);
105
+ padding: 1px 5px;
106
+ border-radius: 4px;
107
+ border: 1px solid var(--border-soft);
108
+ }
109
+
110
+ .modal-foot {
111
+ display: flex;
112
+ justify-content: flex-end;
113
+ gap: var(--s-3);
114
+ padding: var(--s-3) var(--s-6);
115
+ border-top: 1px solid var(--border-soft);
116
+ background: var(--bg);
117
+ }
118
+
119
+ /* Nested "Edit repos" disclosure inside the new-session modal */
120
+ .repos-inline-config {
121
+ margin: var(--s-3) 0;
122
+ border: 1px solid var(--border);
123
+ border-radius: var(--r-sm);
124
+ background: var(--bg);
125
+ }
126
+ .repos-inline-config summary {
127
+ cursor: pointer;
128
+ padding: 8px var(--s-3);
129
+ font-size: 12.5px;
130
+ color: var(--ink-mid);
131
+ font-weight: 500;
132
+ user-select: none;
133
+ }
134
+ .repos-inline-config summary::marker { color: var(--ink-mid); }
135
+ .repos-inline-config summary:hover { color: var(--ink); }
136
+ .repos-inline-config[open] summary { border-bottom: 1px solid var(--border); }
137
+ .repos-inline-body {
138
+ padding: var(--s-3);
139
+ background: var(--bg-elev);
140
+ border-radius: 0 0 var(--r-sm) var(--r-sm);
141
+ }
142
+ .repos-inline-actions {
143
+ display: flex;
144
+ gap: var(--s-3);
145
+ align-items: center;
146
+ margin-top: var(--s-3);
147
+ }
148
+
149
+ /* Narrower variant for confirm/prompt — no border under head, tighter padding */
150
+ .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); }
160
+ .modal-dialog input[type="text"] {
161
+ width: 100%;
162
+ max-width: none;
163
+ margin-top: var(--s-2);
164
+ }
165
+ .dialog-msg {
166
+ font-size: 13.5px;
167
+ color: var(--ink-mid);
168
+ line-height: 1.55;
169
+ }
@@ -0,0 +1,10 @@
1
+ /* Narrow viewports: force-collapse sidebar, single-col config grid */
2
+
3
+ @media (max-width: 900px) {
4
+ .app { grid-template-columns: var(--sidebar-w-collapsed) 1fr !important; }
5
+ .sidebar { padding: var(--s-4) var(--s-2); }
6
+ .brand-name, .nav-label, .nav-badge { opacity: 0; pointer-events: none; }
7
+ .main { padding: var(--s-6) var(--s-5) var(--s-5); }
8
+ .page-head { flex-direction: column; gap: var(--s-3); }
9
+ .config-grid { grid-template-columns: 1fr; }
10
+ }