@bakapiano/ccsm 0.22.3 → 0.22.5
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 +225 -225
- 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 +645 -543
- 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 +159 -22
- package/public/js/components/TerminalResizeDebouncer.js +126 -0
- package/public/js/components/TerminalView.js +15 -2
- package/public/js/components/XtermTerminal.js +74 -15
- 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 +199 -80
- 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,30 +285,31 @@
|
|
|
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
|
-
}
|
|
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
295
|
.session-tab {
|
|
296
296
|
appearance: none;
|
|
297
297
|
background: var(--term-tab);
|
|
298
298
|
border: 0;
|
|
299
299
|
border-bottom: 2px solid transparent;
|
|
300
300
|
margin-bottom: -1px; /* overlap container border-bottom */
|
|
301
|
-
padding: 0 10px;
|
|
302
301
|
display: inline-flex;
|
|
303
302
|
align-items: center;
|
|
304
303
|
gap: 6px;
|
|
305
304
|
font: inherit;
|
|
306
305
|
font-size: 12px;
|
|
307
|
-
color: var(--term-tab-text);
|
|
306
|
+
color: var(--term-tab-text);
|
|
308
307
|
cursor: pointer;
|
|
309
308
|
max-width: 200px;
|
|
310
309
|
min-width: 0;
|
|
311
310
|
transition: background-color .12s, color .12s;
|
|
311
|
+
user-select: none;
|
|
312
|
+
position: relative;
|
|
312
313
|
}
|
|
313
314
|
.session-tab:hover { background: var(--term-tab-hover); color: var(--term-on); }
|
|
314
315
|
.session-tab.is-active {
|
|
@@ -316,6 +317,51 @@
|
|
|
316
317
|
color: var(--term-on);
|
|
317
318
|
border-bottom-color: var(--term-surface);
|
|
318
319
|
}
|
|
320
|
+
.session-tab:focus-visible {
|
|
321
|
+
outline: 1px solid var(--accent);
|
|
322
|
+
outline-offset: -2px;
|
|
323
|
+
}
|
|
324
|
+
.session-tab[data-dnd-over="true"] {
|
|
325
|
+
box-shadow: inset 2px 0 0 var(--accent);
|
|
326
|
+
}
|
|
327
|
+
.session-tab::before {
|
|
328
|
+
content: "";
|
|
329
|
+
position: absolute;
|
|
330
|
+
left: 3px;
|
|
331
|
+
top: 0;
|
|
332
|
+
bottom: 0;
|
|
333
|
+
width: 3px;
|
|
334
|
+
border-radius: 0;
|
|
335
|
+
background: var(--ink-faint);
|
|
336
|
+
opacity: .55;
|
|
337
|
+
}
|
|
338
|
+
.session-tab.is-running::before {
|
|
339
|
+
background: var(--green);
|
|
340
|
+
opacity: .9;
|
|
341
|
+
}
|
|
342
|
+
.session-tab.is-working::before {
|
|
343
|
+
background: var(--blue, #4a73a5);
|
|
344
|
+
opacity: .95;
|
|
345
|
+
}
|
|
346
|
+
.session-tab.is-stopped::before {
|
|
347
|
+
background: var(--ink-faint);
|
|
348
|
+
opacity: .45;
|
|
349
|
+
}
|
|
350
|
+
.session-tab-main {
|
|
351
|
+
display: inline-flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
gap: 6px;
|
|
354
|
+
min-width: 0;
|
|
355
|
+
flex: 1 1 auto;
|
|
356
|
+
height: 100%;
|
|
357
|
+
padding: 0 0 0 14px;
|
|
358
|
+
}
|
|
359
|
+
.session-tab-main[draggable="true"] {
|
|
360
|
+
cursor: grab;
|
|
361
|
+
}
|
|
362
|
+
.session-tab-main[draggable="true"]:active {
|
|
363
|
+
cursor: grabbing;
|
|
364
|
+
}
|
|
319
365
|
.session-tab-icon { display: inline-flex; flex-shrink: 0; }
|
|
320
366
|
.session-tab-icon svg { width: 14px; height: 14px; }
|
|
321
367
|
.session-tab-icon img { width: 14px; height: 14px; }
|
|
@@ -325,14 +371,41 @@
|
|
|
325
371
|
text-overflow: ellipsis;
|
|
326
372
|
min-width: 0;
|
|
327
373
|
}
|
|
328
|
-
.session-tab-
|
|
329
|
-
|
|
374
|
+
.session-tab-close {
|
|
375
|
+
appearance: none;
|
|
376
|
+
border: 0;
|
|
377
|
+
background: transparent;
|
|
378
|
+
color: currentColor;
|
|
379
|
+
display: inline-flex;
|
|
380
|
+
align-items: center;
|
|
381
|
+
justify-content: center;
|
|
382
|
+
width: 18px;
|
|
383
|
+
height: 18px;
|
|
384
|
+
margin-right: 5px;
|
|
385
|
+
border-radius: 3px;
|
|
386
|
+
opacity: 0;
|
|
387
|
+
flex: 0 0 auto;
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
}
|
|
390
|
+
.session-tab:hover .session-tab-close,
|
|
391
|
+
.session-tab.is-active .session-tab-close,
|
|
392
|
+
.session-tab:focus-within .session-tab-close {
|
|
393
|
+
opacity: .72;
|
|
394
|
+
}
|
|
395
|
+
.session-tab-close:hover {
|
|
396
|
+
opacity: 1;
|
|
397
|
+
background: rgba(255, 255, 255, 0.12);
|
|
398
|
+
}
|
|
399
|
+
.session-tab-close svg {
|
|
400
|
+
width: 12px;
|
|
401
|
+
height: 12px;
|
|
402
|
+
}
|
|
330
403
|
.session-tab-add {
|
|
331
404
|
background: transparent;
|
|
332
405
|
max-width: none;
|
|
333
406
|
padding: 0 8px;
|
|
334
|
-
color: #fff;
|
|
335
|
-
}
|
|
407
|
+
color: #fff;
|
|
408
|
+
}
|
|
336
409
|
.session-tab-add:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
|
|
337
410
|
.session-tab-add svg { width: 14px; height: 14px; }
|
|
338
411
|
|
|
@@ -360,56 +433,56 @@
|
|
|
360
433
|
/* Kebab in the page-title-bar (top-right). Compact 24px square so it
|
|
361
434
|
doesn't dominate the masthead. In WCO mode the title-bar already
|
|
362
435
|
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. */
|
|
436
|
+
the left of them. */
|
|
437
|
+
.session-menu-btn {
|
|
438
|
+
appearance: none;
|
|
439
|
+
background: transparent;
|
|
440
|
+
border: 0;
|
|
441
|
+
width: 26px;
|
|
442
|
+
height: 26px;
|
|
443
|
+
border-radius: 5px;
|
|
444
|
+
display: inline-flex;
|
|
445
|
+
align-items: center;
|
|
446
|
+
justify-content: center;
|
|
447
|
+
/* Follow the terminal foreground so the dots read on both the light
|
|
448
|
+
(#f0f0f0) and dark (#252526) tab strip. The old hardcoded #fff was
|
|
449
|
+
invisible on the light strip. */
|
|
450
|
+
color: var(--term-on);
|
|
451
|
+
cursor: pointer;
|
|
452
|
+
flex-shrink: 0;
|
|
453
|
+
transition: background-color .12s, color .12s;
|
|
454
|
+
}
|
|
455
|
+
/* Neutral-grey hover tint works on either strip colour (darkens the light
|
|
456
|
+
one, lightens the dark one) without needing a per-theme override. */
|
|
384
457
|
.session-menu-btn:hover:not(:disabled) { background: rgba(128, 128, 128, 0.2); color: var(--term-on); }
|
|
385
458
|
.session-menu-btn:disabled { opacity: .55; cursor: wait; }
|
|
386
459
|
.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
|
-
}
|
|
460
|
+
|
|
461
|
+
.session-menu {
|
|
462
|
+
background: var(--bg-elev);
|
|
463
|
+
border: 1px solid var(--border);
|
|
464
|
+
border-radius: 6px;
|
|
465
|
+
padding: 4px;
|
|
466
|
+
box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,0.08));
|
|
467
|
+
display: flex;
|
|
468
|
+
flex-direction: column;
|
|
469
|
+
gap: 2px;
|
|
470
|
+
}
|
|
471
|
+
.session-menu-item {
|
|
472
|
+
appearance: none;
|
|
473
|
+
background: transparent;
|
|
474
|
+
border: 0;
|
|
475
|
+
padding: 7px 10px;
|
|
476
|
+
border-radius: 4px;
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
gap: 8px;
|
|
480
|
+
font: inherit;
|
|
481
|
+
font-size: 13px;
|
|
482
|
+
color: var(--ink);
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
text-align: left;
|
|
485
|
+
}
|
|
413
486
|
.session-menu-item:hover { background: var(--bg); }
|
|
414
487
|
.session-menu-item.danger { color: var(--danger, #b73f3f); }
|
|
415
488
|
.session-menu-item.danger:hover { background: rgba(183, 63, 63, 0.08); }
|
|
@@ -430,220 +503,249 @@
|
|
|
430
503
|
}
|
|
431
504
|
|
|
432
505
|
.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
|
-
}
|
|
506
|
+
display: flex;
|
|
507
|
+
align-items: center;
|
|
508
|
+
gap: var(--s-3);
|
|
509
|
+
padding: var(--s-3) var(--s-4);
|
|
510
|
+
border-bottom: 1px solid var(--border);
|
|
511
|
+
background: var(--bg);
|
|
512
|
+
flex-wrap: wrap;
|
|
513
|
+
}
|
|
514
|
+
.session-pane-title {
|
|
515
|
+
display: flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
gap: var(--s-2);
|
|
518
|
+
}
|
|
519
|
+
.session-pane-title h2 {
|
|
520
|
+
margin: 0;
|
|
521
|
+
font-size: 14.5px;
|
|
522
|
+
font-weight: 600;
|
|
523
|
+
}
|
|
524
|
+
.session-pane-meta {
|
|
525
|
+
display: flex;
|
|
526
|
+
gap: var(--s-3);
|
|
527
|
+
align-items: center;
|
|
528
|
+
font-size: 11.5px;
|
|
529
|
+
flex: 1;
|
|
530
|
+
overflow: hidden;
|
|
531
|
+
}
|
|
532
|
+
.session-pane-meta .mono {
|
|
533
|
+
font-family: var(--mono);
|
|
534
|
+
color: var(--ink-mid);
|
|
535
|
+
white-space: nowrap;
|
|
536
|
+
overflow: hidden;
|
|
537
|
+
text-overflow: ellipsis;
|
|
538
|
+
max-width: 50%;
|
|
539
|
+
}
|
|
540
|
+
.session-pane-meta .muted {
|
|
541
|
+
color: var(--ink-muted);
|
|
542
|
+
}
|
|
543
|
+
.session-pane-actions {
|
|
544
|
+
display: flex;
|
|
545
|
+
gap: var(--s-2);
|
|
546
|
+
flex-shrink: 0;
|
|
547
|
+
}
|
|
475
548
|
.session-pane-body {
|
|
476
549
|
flex: 1;
|
|
477
550
|
min-height: 0;
|
|
478
551
|
background: var(--term-surface);
|
|
552
|
+
position: relative;
|
|
553
|
+
overflow: hidden;
|
|
479
554
|
}
|
|
480
|
-
.
|
|
481
|
-
|
|
555
|
+
.terminal-stack {
|
|
556
|
+
position: absolute;
|
|
557
|
+
inset: 0;
|
|
558
|
+
min-width: 0;
|
|
559
|
+
min-height: 0;
|
|
482
560
|
}
|
|
483
|
-
.
|
|
484
|
-
|
|
485
|
-
|
|
561
|
+
.terminal-layer {
|
|
562
|
+
position: absolute;
|
|
563
|
+
inset: 0;
|
|
564
|
+
min-width: 0;
|
|
565
|
+
min-height: 0;
|
|
486
566
|
display: flex;
|
|
487
567
|
flex-direction: column;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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);
|
|
568
|
+
visibility: hidden;
|
|
569
|
+
pointer-events: none;
|
|
570
|
+
z-index: 0;
|
|
501
571
|
}
|
|
502
|
-
.
|
|
503
|
-
|
|
504
|
-
|
|
572
|
+
.terminal-layer.is-active {
|
|
573
|
+
visibility: visible;
|
|
574
|
+
pointer-events: auto;
|
|
575
|
+
z-index: 1;
|
|
505
576
|
}
|
|
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;
|
|
577
|
+
.session-pane-body .terminal-host {
|
|
517
578
|
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
579
|
}
|
|
540
|
-
.terminal-
|
|
541
|
-
|
|
542
|
-
|
|
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;
|
|
580
|
+
.terminal-layer .terminal-host {
|
|
581
|
+
flex: 1 1 auto;
|
|
582
|
+
min-height: 0;
|
|
643
583
|
height: auto;
|
|
644
|
-
min-width: 0;
|
|
645
|
-
padding: 7px 4px;
|
|
646
|
-
gap: 2px;
|
|
647
584
|
}
|
|
648
|
-
.
|
|
649
|
-
|
|
585
|
+
.session-pane-body .terminal-empty {
|
|
586
|
+
background: var(--term-surface);
|
|
587
|
+
color: var(--term-on);
|
|
588
|
+
display: flex;
|
|
589
|
+
flex-direction: column;
|
|
590
|
+
align-items: center;
|
|
591
|
+
justify-content: center;
|
|
592
|
+
gap: var(--s-3);
|
|
593
|
+
height: 100%;
|
|
594
|
+
font-size: 13px;
|
|
595
|
+
}
|
|
596
|
+
.session-pane-body .terminal-empty .mono {
|
|
597
|
+
color: var(--term-prompt);
|
|
598
|
+
}
|
|
599
|
+
.session-pane-body .terminal-empty .action.primary {
|
|
600
|
+
background: var(--term-cta-bg);
|
|
601
|
+
color: var(--term-cta-fg);
|
|
602
|
+
border-color: var(--term-cta-bg);
|
|
603
|
+
}
|
|
604
|
+
.session-pane-body .terminal-empty .action.primary:hover {
|
|
605
|
+
background: var(--term-cta-bg-hover);
|
|
606
|
+
border-color: var(--term-cta-bg-hover);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* Displaced state — shown when the server kicks us off because another
|
|
610
|
+
client attached to the same session (latest-wins). Same dark surface
|
|
611
|
+
as terminal-empty so the transition from running terminal → displaced
|
|
612
|
+
doesn't flash a colour change. */
|
|
613
|
+
.terminal-displaced {
|
|
614
|
+
background: var(--term-surface);
|
|
615
|
+
color: var(--term-on);
|
|
616
|
+
display: flex;
|
|
617
|
+
align-items: center;
|
|
618
|
+
justify-content: center;
|
|
619
|
+
height: 100%;
|
|
620
|
+
padding: var(--s-5);
|
|
621
|
+
}
|
|
622
|
+
.terminal-displaced-card {
|
|
623
|
+
max-width: 460px;
|
|
624
|
+
text-align: center;
|
|
625
|
+
display: flex;
|
|
626
|
+
flex-direction: column;
|
|
627
|
+
gap: var(--s-3);
|
|
628
|
+
}
|
|
629
|
+
.terminal-displaced-card h2 {
|
|
630
|
+
margin: 0;
|
|
631
|
+
font-size: 16px;
|
|
632
|
+
font-weight: 600;
|
|
633
|
+
color: var(--term-heading);
|
|
634
|
+
letter-spacing: -0.005em;
|
|
635
|
+
}
|
|
636
|
+
.terminal-displaced-card p {
|
|
637
|
+
margin: 0;
|
|
638
|
+
font-size: 13px;
|
|
639
|
+
line-height: 1.55;
|
|
640
|
+
color: var(--term-on-dim);
|
|
641
|
+
}
|
|
642
|
+
.terminal-displaced-actions {
|
|
643
|
+
margin-top: var(--s-2);
|
|
644
|
+
display: flex;
|
|
645
|
+
justify-content: center;
|
|
646
|
+
}
|
|
647
|
+
.terminal-displaced-card .action.primary {
|
|
648
|
+
background: var(--term-cta-bg);
|
|
649
|
+
color: var(--term-cta-fg);
|
|
650
|
+
border-color: var(--term-cta-bg);
|
|
651
|
+
padding: 9px 20px;
|
|
652
|
+
font-size: 13px;
|
|
653
|
+
}
|
|
654
|
+
.terminal-displaced-card .action.primary:hover {
|
|
655
|
+
background: var(--term-cta-bg-hover);
|
|
656
|
+
border-color: var(--term-cta-bg-hover);
|
|
657
|
+
}
|
|
658
|
+
.terminal-displaced-hint {
|
|
659
|
+
font-size: 11.5px !important;
|
|
660
|
+
color: var(--term-on-faint) !important;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/* ─── Mobile terminal accessory bar (TerminalKeyBar.js) ───────────────
|
|
664
|
+
Floats just above the soft keyboard via a JS-set `bottom` offset
|
|
665
|
+
(visualViewport keyboard height). Styled against the dark terminal
|
|
666
|
+
palette — it visually belongs to the terminal, not the cream chrome —
|
|
667
|
+
so it reads as one surface with the xterm canvas above it. */
|
|
668
|
+
.term-keybar {
|
|
669
|
+
position: fixed;
|
|
670
|
+
left: 0;
|
|
671
|
+
right: 0;
|
|
672
|
+
z-index: 215; /* above the mobile FAB (210) */
|
|
673
|
+
background: var(--term-keybar-bg);
|
|
674
|
+
border-top: 1px solid var(--term-key-border);
|
|
675
|
+
padding: 6px 8px;
|
|
676
|
+
touch-action: manipulation; /* kill the 300ms double-tap-zoom delay */
|
|
677
|
+
user-select: none;
|
|
678
|
+
-webkit-user-select: none;
|
|
679
|
+
/* NOT overflow:auto here — that would clip the Ctrl popover (which sits
|
|
680
|
+
at bottom:100%, above the bar). The horizontal scroll lives on the
|
|
681
|
+
inner .term-keybar-row instead. */
|
|
682
|
+
}
|
|
683
|
+
/* Inner scroll row — holds the keys; scrolls horizontally if they don't
|
|
684
|
+
fit, without clipping the popover that escapes the bar upward. */
|
|
685
|
+
.term-keybar-row {
|
|
686
|
+
display: flex;
|
|
687
|
+
gap: 6px;
|
|
688
|
+
align-items: center;
|
|
689
|
+
overflow-x: auto;
|
|
690
|
+
-webkit-overflow-scrolling: touch;
|
|
691
|
+
white-space: nowrap;
|
|
692
|
+
}
|
|
693
|
+
.term-keybar-row::-webkit-scrollbar { display: none; }
|
|
694
|
+
|
|
695
|
+
.tkb-key {
|
|
696
|
+
flex: 0 0 auto;
|
|
697
|
+
min-width: 42px;
|
|
698
|
+
height: 38px;
|
|
699
|
+
display: inline-flex;
|
|
700
|
+
align-items: center;
|
|
701
|
+
justify-content: center;
|
|
702
|
+
padding: 0 12px;
|
|
703
|
+
font-family: var(--mono);
|
|
704
|
+
font-size: 13px;
|
|
705
|
+
line-height: 1;
|
|
706
|
+
color: var(--term-key-fg);
|
|
707
|
+
background: var(--term-key-bg);
|
|
708
|
+
border: 1px solid var(--term-key-border);
|
|
709
|
+
border-radius: 8px;
|
|
710
|
+
touch-action: manipulation;
|
|
711
|
+
-webkit-tap-highlight-color: transparent;
|
|
712
|
+
}
|
|
713
|
+
.tkb-key:active,
|
|
714
|
+
.tkb-key.is-active {
|
|
715
|
+
background: var(--term-key-active-bg);
|
|
716
|
+
border-color: var(--term-key-active-border);
|
|
717
|
+
}
|
|
718
|
+
.tkb-arrow { padding: 0 10px; }
|
|
719
|
+
.tkb-arrow svg { width: 18px; height: 18px; }
|
|
720
|
+
/* S-Tab carries a multi-char label — let it size to content. */
|
|
721
|
+
.tkb-wide { padding: 0 12px; }
|
|
722
|
+
/* The ↵ glyph renders a touch small in the mono stack; bump it so it
|
|
723
|
+
matches the arrow icons' optical weight. */
|
|
724
|
+
.tkb-glyph { font-size: 17px; line-height: 1; }
|
|
725
|
+
|
|
726
|
+
/* Ctrl combos — a wrap grid that pops ABOVE the bar (bottom:100%). */
|
|
727
|
+
.term-keybar-pop {
|
|
728
|
+
position: absolute;
|
|
729
|
+
bottom: 100%;
|
|
730
|
+
left: 8px;
|
|
731
|
+
right: 8px;
|
|
732
|
+
z-index: 1; /* above the key row inside the bar's context */
|
|
733
|
+
margin-bottom: 6px;
|
|
734
|
+
display: grid;
|
|
735
|
+
grid-template-columns: repeat(5, 1fr);
|
|
736
|
+
gap: 6px;
|
|
737
|
+
padding: 8px;
|
|
738
|
+
background: var(--term-pop-bg);
|
|
739
|
+
border: 1px solid var(--term-pop-border);
|
|
740
|
+
border-radius: 10px;
|
|
741
|
+
box-shadow: 0 -8px 24px -8px rgba(0, 0, 0, 0.5);
|
|
742
|
+
}
|
|
743
|
+
.tkb-combo {
|
|
744
|
+
flex-direction: column;
|
|
745
|
+
height: auto;
|
|
746
|
+
min-width: 0;
|
|
747
|
+
padding: 7px 4px;
|
|
748
|
+
gap: 2px;
|
|
749
|
+
}
|
|
750
|
+
.tkb-combo-label { font-family: var(--mono); font-size: 13px; color: var(--term-key-fg); }
|
|
751
|
+
.tkb-combo-hint { font-size: 9.5px; color: var(--term-key-hint); letter-spacing: 0.01em; }
|