@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/terminals.css
CHANGED
|
@@ -1,283 +1,283 @@
|
|
|
1
|
-
/* Terminals tab · left rail (active sessions) + right pane (xterm host) */
|
|
2
|
-
|
|
3
|
-
/* Terminal-chrome palette. The xterm canvas itself is painted from a JS
|
|
4
|
-
theme object (TerminalView.js); these vars colour everything AROUND it —
|
|
5
|
-
the pane backdrop, the tab strip, the empty/displaced states, and the
|
|
6
|
-
mobile key bar — so the chrome tracks the canvas. Light defaults here;
|
|
7
|
-
dark.css overrides under [data-theme="dark"] with the original dark set.
|
|
8
|
-
Keep the two in lockstep with the JS THEME_LIGHT / THEME_DARK objects. */
|
|
9
|
-
:root {
|
|
10
|
-
/* VSCode Light+ neutral panel grays, to sit seamlessly around the
|
|
11
|
-
white VSCode terminal canvas (THEME_LIGHT). */
|
|
12
|
-
--term-surface: #ffffff; /* matches THEME_LIGHT.background */
|
|
13
|
-
--term-on: #333333;
|
|
14
|
-
--term-on-dim: rgba(51, 51, 51, 0.70);
|
|
15
|
-
--term-on-faint: rgba(51, 51, 51, 0.45);
|
|
16
|
-
--term-heading: #1a1a1a;
|
|
17
|
-
--term-prompt: #cd3131; /* VSCode ansiRed */
|
|
18
|
-
--term-cta-bg: #2c2c2c;
|
|
19
|
-
--term-cta-fg: #ffffff;
|
|
20
|
-
--term-cta-bg-hover: #000000;
|
|
21
|
-
--term-tabstrip: #f0f0f0;
|
|
22
|
-
--term-tab: #e4e4e4;
|
|
23
|
-
--term-tab-hover: #d8d8d8;
|
|
24
|
-
--term-tab-text: rgba(51, 51, 51, 0.70);
|
|
25
|
-
--term-keybar-bg: #f0f0f0;
|
|
26
|
-
--term-key-fg: #333333;
|
|
27
|
-
--term-key-bg: rgba(0, 0, 0, 0.05);
|
|
28
|
-
--term-key-border: rgba(0, 0, 0, 0.14);
|
|
29
|
-
--term-key-active-bg: rgba(0, 0, 0, 0.12);
|
|
30
|
-
--term-key-active-border: rgba(0, 0, 0, 0.28);
|
|
31
|
-
--term-key-hint: rgba(0, 0, 0, 0.5);
|
|
32
|
-
--term-pop-bg: #f6f6f6;
|
|
33
|
-
--term-pop-border: rgba(0, 0, 0, 0.16);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.terminals-layout {
|
|
37
|
-
display: grid;
|
|
38
|
-
grid-template-columns: 240px 1fr;
|
|
39
|
-
gap: var(--s-4);
|
|
40
|
-
/* viewport minus page header + footer + padding; lets xterm fill height */
|
|
41
|
-
height: calc(100vh - 220px);
|
|
42
|
-
min-height: 480px;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.terminals-rail {
|
|
46
|
-
background: var(--bg-elev);
|
|
47
|
-
border: 1px solid var(--border);
|
|
48
|
-
border-radius: var(--r-md);
|
|
49
|
-
padding: var(--s-2);
|
|
50
|
-
display: flex;
|
|
51
|
-
flex-direction: column;
|
|
52
|
-
gap: 2px;
|
|
53
|
-
overflow-y: auto;
|
|
54
|
-
}
|
|
55
|
-
.terminals-rail-head {
|
|
56
|
-
display: flex;
|
|
57
|
-
justify-content: space-between;
|
|
58
|
-
align-items: center;
|
|
59
|
-
padding: var(--s-2) var(--s-3);
|
|
60
|
-
font-size: 11px;
|
|
61
|
-
text-transform: uppercase;
|
|
62
|
-
letter-spacing: 0.06em;
|
|
63
|
-
color: var(--ink-muted);
|
|
64
|
-
border-bottom: 1px solid var(--border-soft);
|
|
65
|
-
margin-bottom: var(--s-2);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/* button row showing one terminal in the rail */
|
|
69
|
-
.terminal-row {
|
|
70
|
-
appearance: none;
|
|
71
|
-
background: transparent;
|
|
72
|
-
border: 0;
|
|
73
|
-
width: 100%;
|
|
74
|
-
text-align: left;
|
|
75
|
-
padding: 8px 10px;
|
|
76
|
-
border-radius: var(--r-sm);
|
|
77
|
-
cursor: pointer;
|
|
78
|
-
display: grid;
|
|
79
|
-
grid-template-columns: 10px 1fr auto auto;
|
|
80
|
-
align-items: center;
|
|
81
|
-
gap: 8px;
|
|
82
|
-
color: var(--ink-mid);
|
|
83
|
-
font-family: var(--body);
|
|
84
|
-
font-size: 13px;
|
|
85
|
-
transition: background .12s ease, color .12s ease;
|
|
86
|
-
}
|
|
87
|
-
.terminal-row:hover { background: var(--bg); color: var(--ink); }
|
|
88
|
-
.terminal-row.is-active {
|
|
89
|
-
background: var(--sidebar-active);
|
|
90
|
-
color: var(--ink);
|
|
91
|
-
}
|
|
92
|
-
.terminal-row-title {
|
|
93
|
-
white-space: nowrap;
|
|
94
|
-
overflow: hidden;
|
|
95
|
-
text-overflow: ellipsis;
|
|
96
|
-
min-width: 0;
|
|
97
|
-
font-weight: 500;
|
|
98
|
-
}
|
|
99
|
-
.terminal-row-meta {
|
|
100
|
-
font-family: var(--mono);
|
|
101
|
-
font-size: 10.5px;
|
|
102
|
-
color: var(--ink-muted);
|
|
103
|
-
font-variant-numeric: tabular-nums;
|
|
104
|
-
}
|
|
105
|
-
.terminal-row-actions { display: inline-flex; }
|
|
106
|
-
.terminal-row .action.tiny.danger {
|
|
107
|
-
padding: 1px 7px;
|
|
108
|
-
font-size: 11px;
|
|
109
|
-
min-width: 0;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/* right pane — full-height xterm host */
|
|
113
|
-
.terminals-main {
|
|
114
|
-
background: var(--bg);
|
|
115
|
-
border: 1px solid var(--border);
|
|
116
|
-
border-radius: var(--r-md);
|
|
117
|
-
padding: var(--s-3);
|
|
118
|
-
overflow: hidden;
|
|
119
|
-
min-width: 0;
|
|
120
|
-
display: flex;
|
|
121
|
-
flex-direction: column;
|
|
122
|
-
}
|
|
123
|
-
.terminal-host {
|
|
124
|
-
flex: 1;
|
|
125
|
-
min-height: 0;
|
|
126
|
-
width: 100%;
|
|
127
|
-
/* IME composition (Chinese/Japanese pinyin) lives in absolutely-positioned
|
|
128
|
-
.xterm-helper-textarea + .composition-view at the cursor. xterm 5.5 caps
|
|
129
|
-
the view's width to the remaining columns, but as a belt-and-braces guard
|
|
130
|
-
we still clip here so any residual right-edge overflow is absorbed rather
|
|
131
|
-
than expanding the page (it used to trigger a horizontal scrollbar that
|
|
132
|
-
"pushed" the layout). Do NOT touch the textarea/composition-view's own
|
|
133
|
-
text properties — xterm relies on their single-line behaviour to keep IME
|
|
134
|
-
events firing (forcing pre-wrap / break-all eats compositionupdate events
|
|
135
|
-
in Chromium and Chinese input stops working entirely). */
|
|
136
|
-
overflow: hidden;
|
|
137
|
-
contain: layout;
|
|
138
|
-
}
|
|
139
|
-
/* IME composition box. While you're typing pinyin — before committing the
|
|
140
|
-
Chinese characters — xterm writes the in-progress string into
|
|
141
|
-
.composition-view at the cursor. VSCode's terminal shows this box in the
|
|
142
|
-
terminal's own colours; we do the same. An earlier version hid it outright
|
|
143
|
-
(opacity:0) to dodge a layout push, which is why the typed letters were
|
|
144
|
-
invisible — but the host's overflow:hidden + contain:layout above already
|
|
145
|
-
absorb any overflow, and xterm caps the box width, so hiding it was both
|
|
146
|
-
unnecessary and the bug. The helper textarea is left exactly where xterm
|
|
147
|
-
puts it (at the cursor, opacity:0) so the OS IME candidate popup anchors
|
|
148
|
-
correctly — we no longer move or restyle it. */
|
|
149
|
-
.terminal-host .composition-view {
|
|
150
|
-
/* !important to beat xterm.css's own `.xterm .composition-view`
|
|
151
|
-
(#000/#FFF, same specificity, loaded after us). var() still re-resolves
|
|
152
|
-
per theme, so this tracks light/dark automatically. */
|
|
153
|
-
background: var(--term-surface) !important;
|
|
154
|
-
color: var(--term-on) !important;
|
|
155
|
-
border: 1px solid var(--accent);
|
|
156
|
-
border-radius: 3px;
|
|
157
|
-
padding: 0 3px;
|
|
158
|
-
z-index: 5;
|
|
159
|
-
}
|
|
160
|
-
/* Don't override xterm's background — its renderer (canvas/WebGL) assumes
|
|
161
|
-
an opaque surface and ghosts on scroll if we force transparent. The
|
|
162
|
-
theme object in TerminalView.js already paints #faf9f5 to match the
|
|
163
|
-
surrounding card. */
|
|
164
|
-
.terminal-host .xterm-viewport {
|
|
165
|
-
scrollbar-width: thin;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.terminal-empty {
|
|
169
|
-
height: 100%;
|
|
170
|
-
display: flex;
|
|
171
|
-
align-items: center;
|
|
172
|
-
justify-content: center;
|
|
173
|
-
font-size: 13px;
|
|
174
|
-
color: var(--ink-muted);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.terminal-empty-page { width: 100%; }
|
|
178
|
-
|
|
179
|
-
/* === v1.0 session pane === */
|
|
180
|
-
|
|
181
|
-
.sessions-empty {
|
|
182
|
-
display: flex;
|
|
183
|
-
align-items: center;
|
|
184
|
-
justify-content: center;
|
|
185
|
-
min-height: 60vh;
|
|
186
|
-
/* Decorative faint hairline grid, only in this empty state — adds
|
|
187
|
-
editorial atmosphere without affecting any real content view. */
|
|
188
|
-
background-image:
|
|
189
|
-
linear-gradient(to right, rgba(216, 212, 198, 0.5) 1px, transparent 1px),
|
|
190
|
-
linear-gradient(to bottom, rgba(216, 212, 198, 0.5) 1px, transparent 1px);
|
|
191
|
-
background-size: 56px 56px;
|
|
192
|
-
background-position: center;
|
|
193
|
-
position: relative;
|
|
194
|
-
}
|
|
195
|
-
.sessions-empty::before,
|
|
196
|
-
.sessions-empty::after {
|
|
197
|
-
content: "";
|
|
198
|
-
position: absolute;
|
|
199
|
-
inset: 0;
|
|
200
|
-
pointer-events: none;
|
|
201
|
-
}
|
|
202
|
-
.sessions-empty::before {
|
|
203
|
-
background: radial-gradient(ellipse at center, transparent 0%, var(--bg) 75%);
|
|
204
|
-
}
|
|
205
|
-
.sessions-empty-card {
|
|
206
|
-
text-align: center;
|
|
207
|
-
padding: var(--s-12) var(--s-10);
|
|
208
|
-
background: var(--bg-elev);
|
|
209
|
-
border: 1px solid var(--border-soft);
|
|
210
|
-
border-radius: 6px;
|
|
211
|
-
max-width: 440px;
|
|
212
|
-
position: relative;
|
|
213
|
-
z-index: 1;
|
|
214
|
-
box-shadow: var(--shadow-lg);
|
|
215
|
-
}
|
|
216
|
-
.sessions-empty-card::before {
|
|
217
|
-
content: "· EMPTY ·";
|
|
218
|
-
display: block;
|
|
219
|
-
font-family: var(--mono);
|
|
220
|
-
font-size: 10px;
|
|
221
|
-
letter-spacing: 0.28em;
|
|
222
|
-
color: var(--ink-faint);
|
|
223
|
-
margin-bottom: var(--s-4);
|
|
224
|
-
}
|
|
225
|
-
.sessions-empty-card h2 {
|
|
226
|
-
margin: 0 0 var(--s-3);
|
|
227
|
-
font-size: 20px;
|
|
228
|
-
font-weight: 600;
|
|
229
|
-
letter-spacing: -0.015em;
|
|
230
|
-
line-height: 1.2;
|
|
231
|
-
color: var(--ink);
|
|
232
|
-
}
|
|
233
|
-
.sessions-empty-card p {
|
|
234
|
-
margin: 0 0 var(--s-5);
|
|
235
|
-
color: var(--ink-mid);
|
|
236
|
-
font-size: 13.5px;
|
|
237
|
-
line-height: 1.55;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.session-pane {
|
|
241
|
-
display: flex;
|
|
242
|
-
flex-direction: column;
|
|
243
|
-
/* Fill the entire content area edge-to-edge — negative horizontal margin
|
|
244
|
-
cancels .main's horizontal padding so the terminal touches the window
|
|
245
|
-
edges (and the sidebar border) with no surrounding chrome. */
|
|
246
|
-
flex: 1;
|
|
247
|
-
min-height: 0;
|
|
248
|
-
height: 100%;
|
|
249
|
-
margin: 0 calc(-1 * var(--s-4));
|
|
250
|
-
background: var(--bg-elev);
|
|
251
|
-
overflow: hidden;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/* Session tabs · sits between the page-title-bar and the session-pane.
|
|
255
|
-
One row of small tab buttons; the active one has a darker bg + accent
|
|
256
|
-
underline. Last child is a "+" button that bounces to /launch. The
|
|
257
|
-
strip is full-bleed (matches .session-pane horizontal extents). */
|
|
258
|
-
.session-tabs {
|
|
259
|
-
display: flex;
|
|
260
|
-
align-items: stretch;
|
|
261
|
-
gap: 0;
|
|
262
|
-
height: 30px;
|
|
263
|
-
flex-shrink: 0;
|
|
264
|
-
padding: 0 2px 0 0;
|
|
265
|
-
/* Negative bottom margin cancels the tab-panel's gap to the
|
|
266
|
-
session-pane underneath, so the strip sits flush against the
|
|
267
|
-
terminal. Negative horizontal margin cancels .main's padding so
|
|
268
|
-
the strip is full-bleed like the terminal underneath. */
|
|
269
|
-
margin: 0 calc(-1 * var(--s-4)) calc(-1 * var(--s-4));
|
|
270
|
-
background: var(--term-tabstrip);
|
|
271
|
-
border-bottom: 0;
|
|
272
|
-
}
|
|
273
|
-
.session-tabs-list {
|
|
274
|
-
display: flex;
|
|
275
|
-
align-items: stretch;
|
|
276
|
-
gap: 0;
|
|
277
|
-
flex: 1;
|
|
278
|
-
min-width: 0;
|
|
279
|
-
overflow: hidden;
|
|
280
|
-
}
|
|
1
|
+
/* Terminals tab · left rail (active sessions) + right pane (xterm host) */
|
|
2
|
+
|
|
3
|
+
/* Terminal-chrome palette. The xterm canvas itself is painted from a JS
|
|
4
|
+
theme object (TerminalView.js); these vars colour everything AROUND it —
|
|
5
|
+
the pane backdrop, the tab strip, the empty/displaced states, and the
|
|
6
|
+
mobile key bar — so the chrome tracks the canvas. Light defaults here;
|
|
7
|
+
dark.css overrides under [data-theme="dark"] with the original dark set.
|
|
8
|
+
Keep the two in lockstep with the JS THEME_LIGHT / THEME_DARK objects. */
|
|
9
|
+
:root {
|
|
10
|
+
/* VSCode Light+ neutral panel grays, to sit seamlessly around the
|
|
11
|
+
white VSCode terminal canvas (THEME_LIGHT). */
|
|
12
|
+
--term-surface: #ffffff; /* matches THEME_LIGHT.background */
|
|
13
|
+
--term-on: #333333;
|
|
14
|
+
--term-on-dim: rgba(51, 51, 51, 0.70);
|
|
15
|
+
--term-on-faint: rgba(51, 51, 51, 0.45);
|
|
16
|
+
--term-heading: #1a1a1a;
|
|
17
|
+
--term-prompt: #cd3131; /* VSCode ansiRed */
|
|
18
|
+
--term-cta-bg: #2c2c2c;
|
|
19
|
+
--term-cta-fg: #ffffff;
|
|
20
|
+
--term-cta-bg-hover: #000000;
|
|
21
|
+
--term-tabstrip: #f0f0f0;
|
|
22
|
+
--term-tab: #e4e4e4;
|
|
23
|
+
--term-tab-hover: #d8d8d8;
|
|
24
|
+
--term-tab-text: rgba(51, 51, 51, 0.70);
|
|
25
|
+
--term-keybar-bg: #f0f0f0;
|
|
26
|
+
--term-key-fg: #333333;
|
|
27
|
+
--term-key-bg: rgba(0, 0, 0, 0.05);
|
|
28
|
+
--term-key-border: rgba(0, 0, 0, 0.14);
|
|
29
|
+
--term-key-active-bg: rgba(0, 0, 0, 0.12);
|
|
30
|
+
--term-key-active-border: rgba(0, 0, 0, 0.28);
|
|
31
|
+
--term-key-hint: rgba(0, 0, 0, 0.5);
|
|
32
|
+
--term-pop-bg: #f6f6f6;
|
|
33
|
+
--term-pop-border: rgba(0, 0, 0, 0.16);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.terminals-layout {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: 240px 1fr;
|
|
39
|
+
gap: var(--s-4);
|
|
40
|
+
/* viewport minus page header + footer + padding; lets xterm fill height */
|
|
41
|
+
height: calc(100vh - 220px);
|
|
42
|
+
min-height: 480px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.terminals-rail {
|
|
46
|
+
background: var(--bg-elev);
|
|
47
|
+
border: 1px solid var(--border);
|
|
48
|
+
border-radius: var(--r-md);
|
|
49
|
+
padding: var(--s-2);
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: 2px;
|
|
53
|
+
overflow-y: auto;
|
|
54
|
+
}
|
|
55
|
+
.terminals-rail-head {
|
|
56
|
+
display: flex;
|
|
57
|
+
justify-content: space-between;
|
|
58
|
+
align-items: center;
|
|
59
|
+
padding: var(--s-2) var(--s-3);
|
|
60
|
+
font-size: 11px;
|
|
61
|
+
text-transform: uppercase;
|
|
62
|
+
letter-spacing: 0.06em;
|
|
63
|
+
color: var(--ink-muted);
|
|
64
|
+
border-bottom: 1px solid var(--border-soft);
|
|
65
|
+
margin-bottom: var(--s-2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* button row showing one terminal in the rail */
|
|
69
|
+
.terminal-row {
|
|
70
|
+
appearance: none;
|
|
71
|
+
background: transparent;
|
|
72
|
+
border: 0;
|
|
73
|
+
width: 100%;
|
|
74
|
+
text-align: left;
|
|
75
|
+
padding: 8px 10px;
|
|
76
|
+
border-radius: var(--r-sm);
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
display: grid;
|
|
79
|
+
grid-template-columns: 10px 1fr auto auto;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 8px;
|
|
82
|
+
color: var(--ink-mid);
|
|
83
|
+
font-family: var(--body);
|
|
84
|
+
font-size: 13px;
|
|
85
|
+
transition: background .12s ease, color .12s ease;
|
|
86
|
+
}
|
|
87
|
+
.terminal-row:hover { background: var(--bg); color: var(--ink); }
|
|
88
|
+
.terminal-row.is-active {
|
|
89
|
+
background: var(--sidebar-active);
|
|
90
|
+
color: var(--ink);
|
|
91
|
+
}
|
|
92
|
+
.terminal-row-title {
|
|
93
|
+
white-space: nowrap;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
text-overflow: ellipsis;
|
|
96
|
+
min-width: 0;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
}
|
|
99
|
+
.terminal-row-meta {
|
|
100
|
+
font-family: var(--mono);
|
|
101
|
+
font-size: 10.5px;
|
|
102
|
+
color: var(--ink-muted);
|
|
103
|
+
font-variant-numeric: tabular-nums;
|
|
104
|
+
}
|
|
105
|
+
.terminal-row-actions { display: inline-flex; }
|
|
106
|
+
.terminal-row .action.tiny.danger {
|
|
107
|
+
padding: 1px 7px;
|
|
108
|
+
font-size: 11px;
|
|
109
|
+
min-width: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* right pane — full-height xterm host */
|
|
113
|
+
.terminals-main {
|
|
114
|
+
background: var(--bg);
|
|
115
|
+
border: 1px solid var(--border);
|
|
116
|
+
border-radius: var(--r-md);
|
|
117
|
+
padding: var(--s-3);
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
min-width: 0;
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
}
|
|
123
|
+
.terminal-host {
|
|
124
|
+
flex: 1;
|
|
125
|
+
min-height: 0;
|
|
126
|
+
width: 100%;
|
|
127
|
+
/* IME composition (Chinese/Japanese pinyin) lives in absolutely-positioned
|
|
128
|
+
.xterm-helper-textarea + .composition-view at the cursor. xterm 5.5 caps
|
|
129
|
+
the view's width to the remaining columns, but as a belt-and-braces guard
|
|
130
|
+
we still clip here so any residual right-edge overflow is absorbed rather
|
|
131
|
+
than expanding the page (it used to trigger a horizontal scrollbar that
|
|
132
|
+
"pushed" the layout). Do NOT touch the textarea/composition-view's own
|
|
133
|
+
text properties — xterm relies on their single-line behaviour to keep IME
|
|
134
|
+
events firing (forcing pre-wrap / break-all eats compositionupdate events
|
|
135
|
+
in Chromium and Chinese input stops working entirely). */
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
contain: layout;
|
|
138
|
+
}
|
|
139
|
+
/* IME composition box. While you're typing pinyin — before committing the
|
|
140
|
+
Chinese characters — xterm writes the in-progress string into
|
|
141
|
+
.composition-view at the cursor. VSCode's terminal shows this box in the
|
|
142
|
+
terminal's own colours; we do the same. An earlier version hid it outright
|
|
143
|
+
(opacity:0) to dodge a layout push, which is why the typed letters were
|
|
144
|
+
invisible — but the host's overflow:hidden + contain:layout above already
|
|
145
|
+
absorb any overflow, and xterm caps the box width, so hiding it was both
|
|
146
|
+
unnecessary and the bug. The helper textarea is left exactly where xterm
|
|
147
|
+
puts it (at the cursor, opacity:0) so the OS IME candidate popup anchors
|
|
148
|
+
correctly — we no longer move or restyle it. */
|
|
149
|
+
.terminal-host .composition-view {
|
|
150
|
+
/* !important to beat xterm.css's own `.xterm .composition-view`
|
|
151
|
+
(#000/#FFF, same specificity, loaded after us). var() still re-resolves
|
|
152
|
+
per theme, so this tracks light/dark automatically. */
|
|
153
|
+
background: var(--term-surface) !important;
|
|
154
|
+
color: var(--term-on) !important;
|
|
155
|
+
border: 1px solid var(--accent);
|
|
156
|
+
border-radius: 3px;
|
|
157
|
+
padding: 0 3px;
|
|
158
|
+
z-index: 5;
|
|
159
|
+
}
|
|
160
|
+
/* Don't override xterm's background — its renderer (canvas/WebGL) assumes
|
|
161
|
+
an opaque surface and ghosts on scroll if we force transparent. The
|
|
162
|
+
theme object in TerminalView.js already paints #faf9f5 to match the
|
|
163
|
+
surrounding card. */
|
|
164
|
+
.terminal-host .xterm-viewport {
|
|
165
|
+
scrollbar-width: thin;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.terminal-empty {
|
|
169
|
+
height: 100%;
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: center;
|
|
173
|
+
font-size: 13px;
|
|
174
|
+
color: var(--ink-muted);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.terminal-empty-page { width: 100%; }
|
|
178
|
+
|
|
179
|
+
/* === v1.0 session pane === */
|
|
180
|
+
|
|
181
|
+
.sessions-empty {
|
|
182
|
+
display: flex;
|
|
183
|
+
align-items: center;
|
|
184
|
+
justify-content: center;
|
|
185
|
+
min-height: 60vh;
|
|
186
|
+
/* Decorative faint hairline grid, only in this empty state — adds
|
|
187
|
+
editorial atmosphere without affecting any real content view. */
|
|
188
|
+
background-image:
|
|
189
|
+
linear-gradient(to right, rgba(216, 212, 198, 0.5) 1px, transparent 1px),
|
|
190
|
+
linear-gradient(to bottom, rgba(216, 212, 198, 0.5) 1px, transparent 1px);
|
|
191
|
+
background-size: 56px 56px;
|
|
192
|
+
background-position: center;
|
|
193
|
+
position: relative;
|
|
194
|
+
}
|
|
195
|
+
.sessions-empty::before,
|
|
196
|
+
.sessions-empty::after {
|
|
197
|
+
content: "";
|
|
198
|
+
position: absolute;
|
|
199
|
+
inset: 0;
|
|
200
|
+
pointer-events: none;
|
|
201
|
+
}
|
|
202
|
+
.sessions-empty::before {
|
|
203
|
+
background: radial-gradient(ellipse at center, transparent 0%, var(--bg) 75%);
|
|
204
|
+
}
|
|
205
|
+
.sessions-empty-card {
|
|
206
|
+
text-align: center;
|
|
207
|
+
padding: var(--s-12) var(--s-10);
|
|
208
|
+
background: var(--bg-elev);
|
|
209
|
+
border: 1px solid var(--border-soft);
|
|
210
|
+
border-radius: 6px;
|
|
211
|
+
max-width: 440px;
|
|
212
|
+
position: relative;
|
|
213
|
+
z-index: 1;
|
|
214
|
+
box-shadow: var(--shadow-lg);
|
|
215
|
+
}
|
|
216
|
+
.sessions-empty-card::before {
|
|
217
|
+
content: "· EMPTY ·";
|
|
218
|
+
display: block;
|
|
219
|
+
font-family: var(--mono);
|
|
220
|
+
font-size: 10px;
|
|
221
|
+
letter-spacing: 0.28em;
|
|
222
|
+
color: var(--ink-faint);
|
|
223
|
+
margin-bottom: var(--s-4);
|
|
224
|
+
}
|
|
225
|
+
.sessions-empty-card h2 {
|
|
226
|
+
margin: 0 0 var(--s-3);
|
|
227
|
+
font-size: 20px;
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
letter-spacing: -0.015em;
|
|
230
|
+
line-height: 1.2;
|
|
231
|
+
color: var(--ink);
|
|
232
|
+
}
|
|
233
|
+
.sessions-empty-card p {
|
|
234
|
+
margin: 0 0 var(--s-5);
|
|
235
|
+
color: var(--ink-mid);
|
|
236
|
+
font-size: 13.5px;
|
|
237
|
+
line-height: 1.55;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.session-pane {
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-direction: column;
|
|
243
|
+
/* Fill the entire content area edge-to-edge — negative horizontal margin
|
|
244
|
+
cancels .main's horizontal padding so the terminal touches the window
|
|
245
|
+
edges (and the sidebar border) with no surrounding chrome. */
|
|
246
|
+
flex: 1;
|
|
247
|
+
min-height: 0;
|
|
248
|
+
height: 100%;
|
|
249
|
+
margin: 0 calc(-1 * var(--s-4));
|
|
250
|
+
background: var(--bg-elev);
|
|
251
|
+
overflow: hidden;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Session tabs · sits between the page-title-bar and the session-pane.
|
|
255
|
+
One row of small tab buttons; the active one has a darker bg + accent
|
|
256
|
+
underline. Last child is a "+" button that bounces to /launch. The
|
|
257
|
+
strip is full-bleed (matches .session-pane horizontal extents). */
|
|
258
|
+
.session-tabs {
|
|
259
|
+
display: flex;
|
|
260
|
+
align-items: stretch;
|
|
261
|
+
gap: 0;
|
|
262
|
+
height: 30px;
|
|
263
|
+
flex-shrink: 0;
|
|
264
|
+
padding: 0 2px 0 0;
|
|
265
|
+
/* Negative bottom margin cancels the tab-panel's gap to the
|
|
266
|
+
session-pane underneath, so the strip sits flush against the
|
|
267
|
+
terminal. Negative horizontal margin cancels .main's padding so
|
|
268
|
+
the strip is full-bleed like the terminal underneath. */
|
|
269
|
+
margin: 0 calc(-1 * var(--s-4)) calc(-1 * var(--s-4));
|
|
270
|
+
background: var(--term-tabstrip);
|
|
271
|
+
border-bottom: 0;
|
|
272
|
+
}
|
|
273
|
+
.session-tabs-list {
|
|
274
|
+
display: flex;
|
|
275
|
+
align-items: stretch;
|
|
276
|
+
gap: 0;
|
|
277
|
+
flex: 1;
|
|
278
|
+
min-width: 0;
|
|
279
|
+
overflow: hidden;
|
|
280
|
+
}
|
|
281
281
|
.session-tabs-right {
|
|
282
282
|
display: flex;
|
|
283
283
|
align-items: center;
|
|
@@ -285,54 +285,54 @@
|
|
|
285
285
|
gap: 4px;
|
|
286
286
|
padding-right: 2px;
|
|
287
287
|
}
|
|
288
|
-
/* Close the gap to the page-title-bar above — only when there IS one.
|
|
289
|
-
In standalone PWA the title-bar is display:none and .session-tabs is
|
|
290
|
-
the first tab-panel child; a negative margin-top there would push
|
|
291
|
-
the strip up over the OS title-bar border. */
|
|
292
|
-
.page-title-bar + .session-tabs {
|
|
293
|
-
margin-top: calc(-1 * var(--s-4));
|
|
294
|
-
}
|
|
295
|
-
.session-tab {
|
|
296
|
-
appearance: none;
|
|
297
|
-
background: var(--term-tab);
|
|
298
|
-
border: 0;
|
|
299
|
-
border-bottom: 2px solid transparent;
|
|
300
|
-
margin-bottom: -1px; /* overlap container border-bottom */
|
|
301
|
-
padding: 0 10px;
|
|
302
|
-
display: inline-flex;
|
|
303
|
-
align-items: center;
|
|
304
|
-
gap: 6px;
|
|
305
|
-
font: inherit;
|
|
306
|
-
font-size: 12px;
|
|
307
|
-
color: var(--term-tab-text);
|
|
308
|
-
cursor: pointer;
|
|
309
|
-
max-width: 200px;
|
|
310
|
-
min-width: 0;
|
|
311
|
-
transition: background-color .12s, color .12s;
|
|
312
|
-
}
|
|
313
|
-
.session-tab:hover { background: var(--term-tab-hover); color: var(--term-on); }
|
|
314
|
-
.session-tab.is-active {
|
|
315
|
-
background: var(--term-surface);
|
|
316
|
-
color: var(--term-on);
|
|
317
|
-
border-bottom-color: var(--term-surface);
|
|
318
|
-
}
|
|
319
|
-
.session-tab-icon { display: inline-flex; flex-shrink: 0; }
|
|
320
|
-
.session-tab-icon svg { width: 14px; height: 14px; }
|
|
321
|
-
.session-tab-icon img { width: 14px; height: 14px; }
|
|
322
|
-
.session-tab-label {
|
|
323
|
-
white-space: nowrap;
|
|
324
|
-
overflow: hidden;
|
|
325
|
-
text-overflow: ellipsis;
|
|
326
|
-
min-width: 0;
|
|
327
|
-
}
|
|
328
|
-
.session-tab-meta { color: var(--term-tab-text); font-size: 11px; }
|
|
329
|
-
.session-tab.is-active .session-tab-meta { color: rgba(255, 255, 255, 0.6); }
|
|
330
|
-
.session-tab-add {
|
|
331
|
-
background: transparent;
|
|
332
|
-
max-width: none;
|
|
333
|
-
padding: 0 8px;
|
|
334
|
-
color: #fff;
|
|
335
|
-
}
|
|
288
|
+
/* Close the gap to the page-title-bar above — only when there IS one.
|
|
289
|
+
In standalone PWA the title-bar is display:none and .session-tabs is
|
|
290
|
+
the first tab-panel child; a negative margin-top there would push
|
|
291
|
+
the strip up over the OS title-bar border. */
|
|
292
|
+
.page-title-bar + .session-tabs {
|
|
293
|
+
margin-top: calc(-1 * var(--s-4));
|
|
294
|
+
}
|
|
295
|
+
.session-tab {
|
|
296
|
+
appearance: none;
|
|
297
|
+
background: var(--term-tab);
|
|
298
|
+
border: 0;
|
|
299
|
+
border-bottom: 2px solid transparent;
|
|
300
|
+
margin-bottom: -1px; /* overlap container border-bottom */
|
|
301
|
+
padding: 0 10px;
|
|
302
|
+
display: inline-flex;
|
|
303
|
+
align-items: center;
|
|
304
|
+
gap: 6px;
|
|
305
|
+
font: inherit;
|
|
306
|
+
font-size: 12px;
|
|
307
|
+
color: var(--term-tab-text);
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
max-width: 200px;
|
|
310
|
+
min-width: 0;
|
|
311
|
+
transition: background-color .12s, color .12s;
|
|
312
|
+
}
|
|
313
|
+
.session-tab:hover { background: var(--term-tab-hover); color: var(--term-on); }
|
|
314
|
+
.session-tab.is-active {
|
|
315
|
+
background: var(--term-surface);
|
|
316
|
+
color: var(--term-on);
|
|
317
|
+
border-bottom-color: var(--term-surface);
|
|
318
|
+
}
|
|
319
|
+
.session-tab-icon { display: inline-flex; flex-shrink: 0; }
|
|
320
|
+
.session-tab-icon svg { width: 14px; height: 14px; }
|
|
321
|
+
.session-tab-icon img { width: 14px; height: 14px; }
|
|
322
|
+
.session-tab-label {
|
|
323
|
+
white-space: nowrap;
|
|
324
|
+
overflow: hidden;
|
|
325
|
+
text-overflow: ellipsis;
|
|
326
|
+
min-width: 0;
|
|
327
|
+
}
|
|
328
|
+
.session-tab-meta { color: var(--term-tab-text); font-size: 11px; }
|
|
329
|
+
.session-tab.is-active .session-tab-meta { color: rgba(255, 255, 255, 0.6); }
|
|
330
|
+
.session-tab-add {
|
|
331
|
+
background: transparent;
|
|
332
|
+
max-width: none;
|
|
333
|
+
padding: 0 8px;
|
|
334
|
+
color: #fff;
|
|
335
|
+
}
|
|
336
336
|
.session-tab-add:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
|
|
337
337
|
.session-tab-add svg { width: 14px; height: 14px; }
|
|
338
338
|
|
|
@@ -360,56 +360,56 @@
|
|
|
360
360
|
/* Kebab in the page-title-bar (top-right). Compact 24px square so it
|
|
361
361
|
doesn't dominate the masthead. In WCO mode the title-bar already
|
|
362
362
|
reserves padding-right for OS controls, so this slides cleanly to
|
|
363
|
-
the left of them. */
|
|
364
|
-
.session-menu-btn {
|
|
365
|
-
appearance: none;
|
|
366
|
-
background: transparent;
|
|
367
|
-
border: 0;
|
|
368
|
-
width: 26px;
|
|
369
|
-
height: 26px;
|
|
370
|
-
border-radius: 5px;
|
|
371
|
-
display: inline-flex;
|
|
372
|
-
align-items: center;
|
|
373
|
-
justify-content: center;
|
|
374
|
-
/* Follow the terminal foreground so the dots read on both the light
|
|
375
|
-
(#f0f0f0) and dark (#252526) tab strip. The old hardcoded #fff was
|
|
376
|
-
invisible on the light strip. */
|
|
377
|
-
color: var(--term-on);
|
|
378
|
-
cursor: pointer;
|
|
379
|
-
flex-shrink: 0;
|
|
380
|
-
transition: background-color .12s, color .12s;
|
|
381
|
-
}
|
|
382
|
-
/* Neutral-grey hover tint works on either strip colour (darkens the light
|
|
383
|
-
one, lightens the dark one) without needing a per-theme override. */
|
|
363
|
+
the left of them. */
|
|
364
|
+
.session-menu-btn {
|
|
365
|
+
appearance: none;
|
|
366
|
+
background: transparent;
|
|
367
|
+
border: 0;
|
|
368
|
+
width: 26px;
|
|
369
|
+
height: 26px;
|
|
370
|
+
border-radius: 5px;
|
|
371
|
+
display: inline-flex;
|
|
372
|
+
align-items: center;
|
|
373
|
+
justify-content: center;
|
|
374
|
+
/* Follow the terminal foreground so the dots read on both the light
|
|
375
|
+
(#f0f0f0) and dark (#252526) tab strip. The old hardcoded #fff was
|
|
376
|
+
invisible on the light strip. */
|
|
377
|
+
color: var(--term-on);
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
flex-shrink: 0;
|
|
380
|
+
transition: background-color .12s, color .12s;
|
|
381
|
+
}
|
|
382
|
+
/* Neutral-grey hover tint works on either strip colour (darkens the light
|
|
383
|
+
one, lightens the dark one) without needing a per-theme override. */
|
|
384
384
|
.session-menu-btn:hover:not(:disabled) { background: rgba(128, 128, 128, 0.2); color: var(--term-on); }
|
|
385
385
|
.session-menu-btn:disabled { opacity: .55; cursor: wait; }
|
|
386
386
|
.session-menu-btn svg { width: 16px; height: 16px; }
|
|
387
|
-
|
|
388
|
-
.session-menu {
|
|
389
|
-
background: var(--bg-elev);
|
|
390
|
-
border: 1px solid var(--border);
|
|
391
|
-
border-radius: 6px;
|
|
392
|
-
padding: 4px;
|
|
393
|
-
box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,0.08));
|
|
394
|
-
display: flex;
|
|
395
|
-
flex-direction: column;
|
|
396
|
-
gap: 2px;
|
|
397
|
-
}
|
|
398
|
-
.session-menu-item {
|
|
399
|
-
appearance: none;
|
|
400
|
-
background: transparent;
|
|
401
|
-
border: 0;
|
|
402
|
-
padding: 7px 10px;
|
|
403
|
-
border-radius: 4px;
|
|
404
|
-
display: flex;
|
|
405
|
-
align-items: center;
|
|
406
|
-
gap: 8px;
|
|
407
|
-
font: inherit;
|
|
408
|
-
font-size: 13px;
|
|
409
|
-
color: var(--ink);
|
|
410
|
-
cursor: pointer;
|
|
411
|
-
text-align: left;
|
|
412
|
-
}
|
|
387
|
+
|
|
388
|
+
.session-menu {
|
|
389
|
+
background: var(--bg-elev);
|
|
390
|
+
border: 1px solid var(--border);
|
|
391
|
+
border-radius: 6px;
|
|
392
|
+
padding: 4px;
|
|
393
|
+
box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,0.08));
|
|
394
|
+
display: flex;
|
|
395
|
+
flex-direction: column;
|
|
396
|
+
gap: 2px;
|
|
397
|
+
}
|
|
398
|
+
.session-menu-item {
|
|
399
|
+
appearance: none;
|
|
400
|
+
background: transparent;
|
|
401
|
+
border: 0;
|
|
402
|
+
padding: 7px 10px;
|
|
403
|
+
border-radius: 4px;
|
|
404
|
+
display: flex;
|
|
405
|
+
align-items: center;
|
|
406
|
+
gap: 8px;
|
|
407
|
+
font: inherit;
|
|
408
|
+
font-size: 13px;
|
|
409
|
+
color: var(--ink);
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
text-align: left;
|
|
412
|
+
}
|
|
413
413
|
.session-menu-item:hover { background: var(--bg); }
|
|
414
414
|
.session-menu-item.danger { color: var(--danger, #b73f3f); }
|
|
415
415
|
.session-menu-item.danger:hover { background: rgba(183, 63, 63, 0.08); }
|
|
@@ -430,220 +430,220 @@
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
.session-pane-head {
|
|
433
|
-
display: flex;
|
|
434
|
-
align-items: center;
|
|
435
|
-
gap: var(--s-3);
|
|
436
|
-
padding: var(--s-3) var(--s-4);
|
|
437
|
-
border-bottom: 1px solid var(--border);
|
|
438
|
-
background: var(--bg);
|
|
439
|
-
flex-wrap: wrap;
|
|
440
|
-
}
|
|
441
|
-
.session-pane-title {
|
|
442
|
-
display: flex;
|
|
443
|
-
align-items: center;
|
|
444
|
-
gap: var(--s-2);
|
|
445
|
-
}
|
|
446
|
-
.session-pane-title h2 {
|
|
447
|
-
margin: 0;
|
|
448
|
-
font-size: 14.5px;
|
|
449
|
-
font-weight: 600;
|
|
450
|
-
}
|
|
451
|
-
.session-pane-meta {
|
|
452
|
-
display: flex;
|
|
453
|
-
gap: var(--s-3);
|
|
454
|
-
align-items: center;
|
|
455
|
-
font-size: 11.5px;
|
|
456
|
-
flex: 1;
|
|
457
|
-
overflow: hidden;
|
|
458
|
-
}
|
|
459
|
-
.session-pane-meta .mono {
|
|
460
|
-
font-family: var(--mono);
|
|
461
|
-
color: var(--ink-mid);
|
|
462
|
-
white-space: nowrap;
|
|
463
|
-
overflow: hidden;
|
|
464
|
-
text-overflow: ellipsis;
|
|
465
|
-
max-width: 50%;
|
|
466
|
-
}
|
|
467
|
-
.session-pane-meta .muted {
|
|
468
|
-
color: var(--ink-muted);
|
|
469
|
-
}
|
|
470
|
-
.session-pane-actions {
|
|
471
|
-
display: flex;
|
|
472
|
-
gap: var(--s-2);
|
|
473
|
-
flex-shrink: 0;
|
|
474
|
-
}
|
|
475
|
-
.session-pane-body {
|
|
476
|
-
flex: 1;
|
|
477
|
-
min-height: 0;
|
|
478
|
-
background: var(--term-surface);
|
|
479
|
-
}
|
|
480
|
-
.session-pane-body .terminal-host {
|
|
481
|
-
height: 100%;
|
|
482
|
-
}
|
|
483
|
-
.session-pane-body .terminal-empty {
|
|
484
|
-
background: var(--term-surface);
|
|
485
|
-
color: var(--term-on);
|
|
486
|
-
display: flex;
|
|
487
|
-
flex-direction: column;
|
|
488
|
-
align-items: center;
|
|
489
|
-
justify-content: center;
|
|
490
|
-
gap: var(--s-3);
|
|
491
|
-
height: 100%;
|
|
492
|
-
font-size: 13px;
|
|
493
|
-
}
|
|
494
|
-
.session-pane-body .terminal-empty .mono {
|
|
495
|
-
color: var(--term-prompt);
|
|
496
|
-
}
|
|
497
|
-
.session-pane-body .terminal-empty .action.primary {
|
|
498
|
-
background: var(--term-cta-bg);
|
|
499
|
-
color: var(--term-cta-fg);
|
|
500
|
-
border-color: var(--term-cta-bg);
|
|
501
|
-
}
|
|
502
|
-
.session-pane-body .terminal-empty .action.primary:hover {
|
|
503
|
-
background: var(--term-cta-bg-hover);
|
|
504
|
-
border-color: var(--term-cta-bg-hover);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/* Displaced state — shown when the server kicks us off because another
|
|
508
|
-
client attached to the same session (latest-wins). Same dark surface
|
|
509
|
-
as terminal-empty so the transition from running terminal → displaced
|
|
510
|
-
doesn't flash a colour change. */
|
|
511
|
-
.terminal-displaced {
|
|
512
|
-
background: var(--term-surface);
|
|
513
|
-
color: var(--term-on);
|
|
514
|
-
display: flex;
|
|
515
|
-
align-items: center;
|
|
516
|
-
justify-content: center;
|
|
517
|
-
height: 100%;
|
|
518
|
-
padding: var(--s-5);
|
|
519
|
-
}
|
|
520
|
-
.terminal-displaced-card {
|
|
521
|
-
max-width: 460px;
|
|
522
|
-
text-align: center;
|
|
523
|
-
display: flex;
|
|
524
|
-
flex-direction: column;
|
|
525
|
-
gap: var(--s-3);
|
|
526
|
-
}
|
|
527
|
-
.terminal-displaced-card h2 {
|
|
528
|
-
margin: 0;
|
|
529
|
-
font-size: 16px;
|
|
530
|
-
font-weight: 600;
|
|
531
|
-
color: var(--term-heading);
|
|
532
|
-
letter-spacing: -0.005em;
|
|
533
|
-
}
|
|
534
|
-
.terminal-displaced-card p {
|
|
535
|
-
margin: 0;
|
|
536
|
-
font-size: 13px;
|
|
537
|
-
line-height: 1.55;
|
|
538
|
-
color: var(--term-on-dim);
|
|
539
|
-
}
|
|
540
|
-
.terminal-displaced-actions {
|
|
541
|
-
margin-top: var(--s-2);
|
|
542
|
-
display: flex;
|
|
543
|
-
justify-content: center;
|
|
544
|
-
}
|
|
545
|
-
.terminal-displaced-card .action.primary {
|
|
546
|
-
background: var(--term-cta-bg);
|
|
547
|
-
color: var(--term-cta-fg);
|
|
548
|
-
border-color: var(--term-cta-bg);
|
|
549
|
-
padding: 9px 20px;
|
|
550
|
-
font-size: 13px;
|
|
551
|
-
}
|
|
552
|
-
.terminal-displaced-card .action.primary:hover {
|
|
553
|
-
background: var(--term-cta-bg-hover);
|
|
554
|
-
border-color: var(--term-cta-bg-hover);
|
|
555
|
-
}
|
|
556
|
-
.terminal-displaced-hint {
|
|
557
|
-
font-size: 11.5px !important;
|
|
558
|
-
color: var(--term-on-faint) !important;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/* ─── Mobile terminal accessory bar (TerminalKeyBar.js) ───────────────
|
|
562
|
-
Floats just above the soft keyboard via a JS-set `bottom` offset
|
|
563
|
-
(visualViewport keyboard height). Styled against the dark terminal
|
|
564
|
-
palette — it visually belongs to the terminal, not the cream chrome —
|
|
565
|
-
so it reads as one surface with the xterm canvas above it. */
|
|
566
|
-
.term-keybar {
|
|
567
|
-
position: fixed;
|
|
568
|
-
left: 0;
|
|
569
|
-
right: 0;
|
|
570
|
-
z-index: 215; /* above the mobile FAB (210) */
|
|
571
|
-
background: var(--term-keybar-bg);
|
|
572
|
-
border-top: 1px solid var(--term-key-border);
|
|
573
|
-
padding: 6px 8px;
|
|
574
|
-
touch-action: manipulation; /* kill the 300ms double-tap-zoom delay */
|
|
575
|
-
user-select: none;
|
|
576
|
-
-webkit-user-select: none;
|
|
577
|
-
/* NOT overflow:auto here — that would clip the Ctrl popover (which sits
|
|
578
|
-
at bottom:100%, above the bar). The horizontal scroll lives on the
|
|
579
|
-
inner .term-keybar-row instead. */
|
|
580
|
-
}
|
|
581
|
-
/* Inner scroll row — holds the keys; scrolls horizontally if they don't
|
|
582
|
-
fit, without clipping the popover that escapes the bar upward. */
|
|
583
|
-
.term-keybar-row {
|
|
584
|
-
display: flex;
|
|
585
|
-
gap: 6px;
|
|
586
|
-
align-items: center;
|
|
587
|
-
overflow-x: auto;
|
|
588
|
-
-webkit-overflow-scrolling: touch;
|
|
589
|
-
white-space: nowrap;
|
|
590
|
-
}
|
|
591
|
-
.term-keybar-row::-webkit-scrollbar { display: none; }
|
|
592
|
-
|
|
593
|
-
.tkb-key {
|
|
594
|
-
flex: 0 0 auto;
|
|
595
|
-
min-width: 42px;
|
|
596
|
-
height: 38px;
|
|
597
|
-
display: inline-flex;
|
|
598
|
-
align-items: center;
|
|
599
|
-
justify-content: center;
|
|
600
|
-
padding: 0 12px;
|
|
601
|
-
font-family: var(--mono);
|
|
602
|
-
font-size: 13px;
|
|
603
|
-
line-height: 1;
|
|
604
|
-
color: var(--term-key-fg);
|
|
605
|
-
background: var(--term-key-bg);
|
|
606
|
-
border: 1px solid var(--term-key-border);
|
|
607
|
-
border-radius: 8px;
|
|
608
|
-
touch-action: manipulation;
|
|
609
|
-
-webkit-tap-highlight-color: transparent;
|
|
610
|
-
}
|
|
611
|
-
.tkb-key:active,
|
|
612
|
-
.tkb-key.is-active {
|
|
613
|
-
background: var(--term-key-active-bg);
|
|
614
|
-
border-color: var(--term-key-active-border);
|
|
615
|
-
}
|
|
616
|
-
.tkb-arrow { padding: 0 10px; }
|
|
617
|
-
.tkb-arrow svg { width: 18px; height: 18px; }
|
|
618
|
-
/* S-Tab carries a multi-char label — let it size to content. */
|
|
619
|
-
.tkb-wide { padding: 0 12px; }
|
|
620
|
-
/* The ↵ glyph renders a touch small in the mono stack; bump it so it
|
|
621
|
-
matches the arrow icons' optical weight. */
|
|
622
|
-
.tkb-glyph { font-size: 17px; line-height: 1; }
|
|
623
|
-
|
|
624
|
-
/* Ctrl combos — a wrap grid that pops ABOVE the bar (bottom:100%). */
|
|
625
|
-
.term-keybar-pop {
|
|
626
|
-
position: absolute;
|
|
627
|
-
bottom: 100%;
|
|
628
|
-
left: 8px;
|
|
629
|
-
right: 8px;
|
|
630
|
-
z-index: 1; /* above the key row inside the bar's context */
|
|
631
|
-
margin-bottom: 6px;
|
|
632
|
-
display: grid;
|
|
633
|
-
grid-template-columns: repeat(5, 1fr);
|
|
634
|
-
gap: 6px;
|
|
635
|
-
padding: 8px;
|
|
636
|
-
background: var(--term-pop-bg);
|
|
637
|
-
border: 1px solid var(--term-pop-border);
|
|
638
|
-
border-radius: 10px;
|
|
639
|
-
box-shadow: 0 -8px 24px -8px rgba(0, 0, 0, 0.5);
|
|
640
|
-
}
|
|
641
|
-
.tkb-combo {
|
|
642
|
-
flex-direction: column;
|
|
643
|
-
height: auto;
|
|
644
|
-
min-width: 0;
|
|
645
|
-
padding: 7px 4px;
|
|
646
|
-
gap: 2px;
|
|
647
|
-
}
|
|
648
|
-
.tkb-combo-label { font-family: var(--mono); font-size: 13px; color: var(--term-key-fg); }
|
|
649
|
-
.tkb-combo-hint { font-size: 9.5px; color: var(--term-key-hint); letter-spacing: 0.01em; }
|
|
433
|
+
display: flex;
|
|
434
|
+
align-items: center;
|
|
435
|
+
gap: var(--s-3);
|
|
436
|
+
padding: var(--s-3) var(--s-4);
|
|
437
|
+
border-bottom: 1px solid var(--border);
|
|
438
|
+
background: var(--bg);
|
|
439
|
+
flex-wrap: wrap;
|
|
440
|
+
}
|
|
441
|
+
.session-pane-title {
|
|
442
|
+
display: flex;
|
|
443
|
+
align-items: center;
|
|
444
|
+
gap: var(--s-2);
|
|
445
|
+
}
|
|
446
|
+
.session-pane-title h2 {
|
|
447
|
+
margin: 0;
|
|
448
|
+
font-size: 14.5px;
|
|
449
|
+
font-weight: 600;
|
|
450
|
+
}
|
|
451
|
+
.session-pane-meta {
|
|
452
|
+
display: flex;
|
|
453
|
+
gap: var(--s-3);
|
|
454
|
+
align-items: center;
|
|
455
|
+
font-size: 11.5px;
|
|
456
|
+
flex: 1;
|
|
457
|
+
overflow: hidden;
|
|
458
|
+
}
|
|
459
|
+
.session-pane-meta .mono {
|
|
460
|
+
font-family: var(--mono);
|
|
461
|
+
color: var(--ink-mid);
|
|
462
|
+
white-space: nowrap;
|
|
463
|
+
overflow: hidden;
|
|
464
|
+
text-overflow: ellipsis;
|
|
465
|
+
max-width: 50%;
|
|
466
|
+
}
|
|
467
|
+
.session-pane-meta .muted {
|
|
468
|
+
color: var(--ink-muted);
|
|
469
|
+
}
|
|
470
|
+
.session-pane-actions {
|
|
471
|
+
display: flex;
|
|
472
|
+
gap: var(--s-2);
|
|
473
|
+
flex-shrink: 0;
|
|
474
|
+
}
|
|
475
|
+
.session-pane-body {
|
|
476
|
+
flex: 1;
|
|
477
|
+
min-height: 0;
|
|
478
|
+
background: var(--term-surface);
|
|
479
|
+
}
|
|
480
|
+
.session-pane-body .terminal-host {
|
|
481
|
+
height: 100%;
|
|
482
|
+
}
|
|
483
|
+
.session-pane-body .terminal-empty {
|
|
484
|
+
background: var(--term-surface);
|
|
485
|
+
color: var(--term-on);
|
|
486
|
+
display: flex;
|
|
487
|
+
flex-direction: column;
|
|
488
|
+
align-items: center;
|
|
489
|
+
justify-content: center;
|
|
490
|
+
gap: var(--s-3);
|
|
491
|
+
height: 100%;
|
|
492
|
+
font-size: 13px;
|
|
493
|
+
}
|
|
494
|
+
.session-pane-body .terminal-empty .mono {
|
|
495
|
+
color: var(--term-prompt);
|
|
496
|
+
}
|
|
497
|
+
.session-pane-body .terminal-empty .action.primary {
|
|
498
|
+
background: var(--term-cta-bg);
|
|
499
|
+
color: var(--term-cta-fg);
|
|
500
|
+
border-color: var(--term-cta-bg);
|
|
501
|
+
}
|
|
502
|
+
.session-pane-body .terminal-empty .action.primary:hover {
|
|
503
|
+
background: var(--term-cta-bg-hover);
|
|
504
|
+
border-color: var(--term-cta-bg-hover);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* Displaced state — shown when the server kicks us off because another
|
|
508
|
+
client attached to the same session (latest-wins). Same dark surface
|
|
509
|
+
as terminal-empty so the transition from running terminal → displaced
|
|
510
|
+
doesn't flash a colour change. */
|
|
511
|
+
.terminal-displaced {
|
|
512
|
+
background: var(--term-surface);
|
|
513
|
+
color: var(--term-on);
|
|
514
|
+
display: flex;
|
|
515
|
+
align-items: center;
|
|
516
|
+
justify-content: center;
|
|
517
|
+
height: 100%;
|
|
518
|
+
padding: var(--s-5);
|
|
519
|
+
}
|
|
520
|
+
.terminal-displaced-card {
|
|
521
|
+
max-width: 460px;
|
|
522
|
+
text-align: center;
|
|
523
|
+
display: flex;
|
|
524
|
+
flex-direction: column;
|
|
525
|
+
gap: var(--s-3);
|
|
526
|
+
}
|
|
527
|
+
.terminal-displaced-card h2 {
|
|
528
|
+
margin: 0;
|
|
529
|
+
font-size: 16px;
|
|
530
|
+
font-weight: 600;
|
|
531
|
+
color: var(--term-heading);
|
|
532
|
+
letter-spacing: -0.005em;
|
|
533
|
+
}
|
|
534
|
+
.terminal-displaced-card p {
|
|
535
|
+
margin: 0;
|
|
536
|
+
font-size: 13px;
|
|
537
|
+
line-height: 1.55;
|
|
538
|
+
color: var(--term-on-dim);
|
|
539
|
+
}
|
|
540
|
+
.terminal-displaced-actions {
|
|
541
|
+
margin-top: var(--s-2);
|
|
542
|
+
display: flex;
|
|
543
|
+
justify-content: center;
|
|
544
|
+
}
|
|
545
|
+
.terminal-displaced-card .action.primary {
|
|
546
|
+
background: var(--term-cta-bg);
|
|
547
|
+
color: var(--term-cta-fg);
|
|
548
|
+
border-color: var(--term-cta-bg);
|
|
549
|
+
padding: 9px 20px;
|
|
550
|
+
font-size: 13px;
|
|
551
|
+
}
|
|
552
|
+
.terminal-displaced-card .action.primary:hover {
|
|
553
|
+
background: var(--term-cta-bg-hover);
|
|
554
|
+
border-color: var(--term-cta-bg-hover);
|
|
555
|
+
}
|
|
556
|
+
.terminal-displaced-hint {
|
|
557
|
+
font-size: 11.5px !important;
|
|
558
|
+
color: var(--term-on-faint) !important;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/* ─── Mobile terminal accessory bar (TerminalKeyBar.js) ───────────────
|
|
562
|
+
Floats just above the soft keyboard via a JS-set `bottom` offset
|
|
563
|
+
(visualViewport keyboard height). Styled against the dark terminal
|
|
564
|
+
palette — it visually belongs to the terminal, not the cream chrome —
|
|
565
|
+
so it reads as one surface with the xterm canvas above it. */
|
|
566
|
+
.term-keybar {
|
|
567
|
+
position: fixed;
|
|
568
|
+
left: 0;
|
|
569
|
+
right: 0;
|
|
570
|
+
z-index: 215; /* above the mobile FAB (210) */
|
|
571
|
+
background: var(--term-keybar-bg);
|
|
572
|
+
border-top: 1px solid var(--term-key-border);
|
|
573
|
+
padding: 6px 8px;
|
|
574
|
+
touch-action: manipulation; /* kill the 300ms double-tap-zoom delay */
|
|
575
|
+
user-select: none;
|
|
576
|
+
-webkit-user-select: none;
|
|
577
|
+
/* NOT overflow:auto here — that would clip the Ctrl popover (which sits
|
|
578
|
+
at bottom:100%, above the bar). The horizontal scroll lives on the
|
|
579
|
+
inner .term-keybar-row instead. */
|
|
580
|
+
}
|
|
581
|
+
/* Inner scroll row — holds the keys; scrolls horizontally if they don't
|
|
582
|
+
fit, without clipping the popover that escapes the bar upward. */
|
|
583
|
+
.term-keybar-row {
|
|
584
|
+
display: flex;
|
|
585
|
+
gap: 6px;
|
|
586
|
+
align-items: center;
|
|
587
|
+
overflow-x: auto;
|
|
588
|
+
-webkit-overflow-scrolling: touch;
|
|
589
|
+
white-space: nowrap;
|
|
590
|
+
}
|
|
591
|
+
.term-keybar-row::-webkit-scrollbar { display: none; }
|
|
592
|
+
|
|
593
|
+
.tkb-key {
|
|
594
|
+
flex: 0 0 auto;
|
|
595
|
+
min-width: 42px;
|
|
596
|
+
height: 38px;
|
|
597
|
+
display: inline-flex;
|
|
598
|
+
align-items: center;
|
|
599
|
+
justify-content: center;
|
|
600
|
+
padding: 0 12px;
|
|
601
|
+
font-family: var(--mono);
|
|
602
|
+
font-size: 13px;
|
|
603
|
+
line-height: 1;
|
|
604
|
+
color: var(--term-key-fg);
|
|
605
|
+
background: var(--term-key-bg);
|
|
606
|
+
border: 1px solid var(--term-key-border);
|
|
607
|
+
border-radius: 8px;
|
|
608
|
+
touch-action: manipulation;
|
|
609
|
+
-webkit-tap-highlight-color: transparent;
|
|
610
|
+
}
|
|
611
|
+
.tkb-key:active,
|
|
612
|
+
.tkb-key.is-active {
|
|
613
|
+
background: var(--term-key-active-bg);
|
|
614
|
+
border-color: var(--term-key-active-border);
|
|
615
|
+
}
|
|
616
|
+
.tkb-arrow { padding: 0 10px; }
|
|
617
|
+
.tkb-arrow svg { width: 18px; height: 18px; }
|
|
618
|
+
/* S-Tab carries a multi-char label — let it size to content. */
|
|
619
|
+
.tkb-wide { padding: 0 12px; }
|
|
620
|
+
/* The ↵ glyph renders a touch small in the mono stack; bump it so it
|
|
621
|
+
matches the arrow icons' optical weight. */
|
|
622
|
+
.tkb-glyph { font-size: 17px; line-height: 1; }
|
|
623
|
+
|
|
624
|
+
/* Ctrl combos — a wrap grid that pops ABOVE the bar (bottom:100%). */
|
|
625
|
+
.term-keybar-pop {
|
|
626
|
+
position: absolute;
|
|
627
|
+
bottom: 100%;
|
|
628
|
+
left: 8px;
|
|
629
|
+
right: 8px;
|
|
630
|
+
z-index: 1; /* above the key row inside the bar's context */
|
|
631
|
+
margin-bottom: 6px;
|
|
632
|
+
display: grid;
|
|
633
|
+
grid-template-columns: repeat(5, 1fr);
|
|
634
|
+
gap: 6px;
|
|
635
|
+
padding: 8px;
|
|
636
|
+
background: var(--term-pop-bg);
|
|
637
|
+
border: 1px solid var(--term-pop-border);
|
|
638
|
+
border-radius: 10px;
|
|
639
|
+
box-shadow: 0 -8px 24px -8px rgba(0, 0, 0, 0.5);
|
|
640
|
+
}
|
|
641
|
+
.tkb-combo {
|
|
642
|
+
flex-direction: column;
|
|
643
|
+
height: auto;
|
|
644
|
+
min-width: 0;
|
|
645
|
+
padding: 7px 4px;
|
|
646
|
+
gap: 2px;
|
|
647
|
+
}
|
|
648
|
+
.tkb-combo-label { font-family: var(--mono); font-size: 13px; color: var(--term-key-fg); }
|
|
649
|
+
.tkb-combo-hint { font-size: 9.5px; color: var(--term-key-hint); letter-spacing: 0.01em; }
|