@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.
Files changed (60) hide show
  1. package/CLAUDE.md +538 -538
  2. package/README.md +189 -189
  3. package/bin/ccsm.js +235 -235
  4. package/lib/cliActivity.js +139 -139
  5. package/lib/codexSeed.js +183 -183
  6. package/lib/config.js +274 -274
  7. package/lib/devices.js +229 -229
  8. package/lib/folders.js +124 -124
  9. package/lib/localCliSessions.js +519 -519
  10. package/lib/persistedSessions.js +129 -129
  11. package/lib/tunnel.js +621 -621
  12. package/lib/webTerminal.js +233 -231
  13. package/lib/workspace.js +233 -233
  14. package/package.json +57 -57
  15. package/public/css/base.css +99 -99
  16. package/public/css/cards.css +183 -183
  17. package/public/css/feedback.css +504 -504
  18. package/public/css/forms.css +453 -453
  19. package/public/css/layout.css +176 -176
  20. package/public/css/modal.css +190 -190
  21. package/public/css/responsive.css +176 -176
  22. package/public/css/sidebar.css +707 -707
  23. package/public/css/terminals.css +592 -592
  24. package/public/css/tokens.css +81 -81
  25. package/public/css/wco.css +196 -196
  26. package/public/css/widgets.css +2725 -2725
  27. package/public/index.html +152 -152
  28. package/public/js/api.js +371 -371
  29. package/public/js/backend.js +149 -149
  30. package/public/js/components/App.js +73 -73
  31. package/public/js/components/DirectoryPicker.js +203 -203
  32. package/public/js/components/EntityFormModal.js +153 -153
  33. package/public/js/components/Modal.js +57 -57
  34. package/public/js/components/OfflineBanner.js +67 -67
  35. package/public/js/components/PageTitleBar.js +13 -13
  36. package/public/js/components/PendingApprovalOverlay.js +128 -128
  37. package/public/js/components/Picker.js +179 -179
  38. package/public/js/components/Popover.js +55 -55
  39. package/public/js/components/RestartOverlay.js +36 -36
  40. package/public/js/components/Sidebar.js +380 -380
  41. package/public/js/components/TerminalInstance.js +187 -15
  42. package/public/js/components/TerminalResizeDebouncer.js +126 -0
  43. package/public/js/components/XtermTerminal.js +148 -14
  44. package/public/js/components/useDragSort.js +67 -67
  45. package/public/js/dialog.js +67 -67
  46. package/public/js/icons.js +212 -212
  47. package/public/js/main.js +296 -296
  48. package/public/js/pages/AboutPage.js +90 -90
  49. package/public/js/pages/ConfigurePage.js +713 -713
  50. package/public/js/pages/LaunchPage.js +421 -421
  51. package/public/js/pages/RemotePage.js +743 -743
  52. package/public/js/pages/SessionsPage.js +100 -100
  53. package/public/js/state.js +335 -335
  54. package/public/manifest.webmanifest +25 -0
  55. package/public/setup/index.html +567 -0
  56. package/scripts/dev.js +149 -149
  57. package/scripts/install.js +153 -153
  58. package/scripts/restart-helper.js +96 -96
  59. package/scripts/upgrade-helper.js +687 -687
  60. package/server.js +1807 -1807
@@ -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; }