clay-server 2.47.0-beta.6 → 3.0.0-beta.2
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/lib/daemon.js +6 -27
- package/lib/project-connection.js +21 -8
- package/lib/project-session-pair.js +312 -0
- package/lib/project-session-spawn.js +8 -4
- package/lib/project-sessions.js +108 -10
- package/lib/project-user-message.js +18 -8
- package/lib/project.js +33 -0
- package/lib/public/app.js +20 -0
- package/lib/public/css/menus.css +83 -1
- package/lib/public/css/messages.css +14 -0
- package/lib/public/css/pane.css +420 -0
- package/lib/public/css/sidebar.css +40 -0
- package/lib/public/css/sticky-notes.css +2 -0
- package/lib/public/index.html +4 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-favicon.js +5 -0
- package/lib/public/modules/app-header.js +101 -6
- package/lib/public/modules/app-messages.js +59 -6
- package/lib/public/modules/app-panels.js +49 -27
- package/lib/public/modules/app-rendering.js +11 -4
- package/lib/public/modules/pane-bridge.js +41 -0
- package/lib/public/modules/pane-session.js +18 -0
- package/lib/public/modules/sidebar-sessions.js +175 -1
- package/lib/public/modules/sidebar.js +2 -0
- package/lib/public/modules/split-group-helpers.js +18 -0
- package/lib/public/modules/split-pair-ui.js +324 -0
- package/lib/public/modules/split-view.js +499 -0
- package/lib/public/modules/sticky-notes.js +32 -91
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +111 -20
- package/lib/sdk-message-processor.js +2 -2
- package/lib/server.js +24 -4
- package/lib/session-hygiene.js +100 -0
- package/lib/session-pair-mcp-server.js +28 -0
- package/lib/session-split-groups.js +298 -0
- package/lib/sessions.js +163 -34
- package/lib/ws-request.js +14 -0
- package/lib/ws-schema.js +13 -0
- package/lib/yoke/adapters/claude.js +32 -0
- package/lib/yoke/adapters/codex.js +100 -77
- package/lib/yoke/adapters/kiro.js +34 -35
- package/lib/yoke/codex-app-server.js +56 -6
- package/lib/yoke/index.js +1 -0
- package/lib/yoke/skill-discovery.js +196 -0
- package/lib/yoke/vendor-registry.js +34 -0
- package/package.json +2 -2
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/* Pane-mode app instances and the parent split shell. */
|
|
2
|
+
|
|
3
|
+
body.pane-mode #top-bar,
|
|
4
|
+
body.pane-mode #icon-strip,
|
|
5
|
+
body.pane-mode #sidebar-column,
|
|
6
|
+
body.pane-mode #mate-sidebar-column,
|
|
7
|
+
body.pane-mode #sidebar-resize-handle,
|
|
8
|
+
body.pane-mode #mate-sidebar-resize-handle,
|
|
9
|
+
body.pane-mode #sidebar-overlay,
|
|
10
|
+
body.pane-mode #home-hub,
|
|
11
|
+
body.pane-mode #whats-new-article,
|
|
12
|
+
body.pane-mode .title-bar-content,
|
|
13
|
+
body.pane-mode .dm-header-bar,
|
|
14
|
+
body.pane-mode #mobile-tab-bar,
|
|
15
|
+
body.pane-mode #mobile-sheet,
|
|
16
|
+
body.pane-mode #user-island {
|
|
17
|
+
display: none !important;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Sticky notes are project-wide. The parent split shell renders one shared
|
|
21
|
+
canvas across the full group instead of duplicating it in each iframe. */
|
|
22
|
+
body.pane-mode #sticky-notes-container {
|
|
23
|
+
display: none !important;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body.pane-mode #main-area {
|
|
27
|
+
border: 0;
|
|
28
|
+
border-radius: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
body.pane-mode #main-column,
|
|
32
|
+
body.pane-mode #main-panels,
|
|
33
|
+
body.pane-mode #app {
|
|
34
|
+
width: 100%;
|
|
35
|
+
height: 100%;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Narrow panes cross the mobile breakpoint, but pane mode hides the mobile
|
|
39
|
+
tab bar. Keep the normal composer inset instead of reserving its 56px. */
|
|
40
|
+
body.pane-mode #input-area {
|
|
41
|
+
padding-bottom: calc(var(--safe-bottom) + 8px);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#main-panels {
|
|
45
|
+
position: relative;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#app.split-native-hidden {
|
|
49
|
+
display: none !important;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#split-host {
|
|
53
|
+
display: none;
|
|
54
|
+
flex: 1;
|
|
55
|
+
min-width: 0;
|
|
56
|
+
min-height: 0;
|
|
57
|
+
background: var(--bg);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#split-host.visible {
|
|
61
|
+
display: flex;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.split-pane {
|
|
65
|
+
flex: 1;
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
min-width: 0;
|
|
69
|
+
min-height: 0;
|
|
70
|
+
border-left: 1px solid var(--border-subtle);
|
|
71
|
+
background: var(--bg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.split-pane:first-child {
|
|
75
|
+
border-left: 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.split-pane-header {
|
|
79
|
+
height: 24px;
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 6px;
|
|
83
|
+
padding: 0 5px 0 9px;
|
|
84
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
85
|
+
background: var(--sidebar-bg);
|
|
86
|
+
color: var(--text-secondary);
|
|
87
|
+
flex-shrink: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.split-pane-vendor {
|
|
91
|
+
width: 14px;
|
|
92
|
+
height: 14px;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.split-pane-title {
|
|
98
|
+
flex: 1;
|
|
99
|
+
min-width: 0;
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
text-overflow: ellipsis;
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
font-size: 11px;
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
cursor: text;
|
|
106
|
+
border-radius: 4px;
|
|
107
|
+
padding: 1px 4px;
|
|
108
|
+
margin-left: -4px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.split-pane-title:hover {
|
|
112
|
+
background: var(--bg-alt);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.split-pair-role,
|
|
116
|
+
.split-pair-live {
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
border-radius: 999px;
|
|
119
|
+
padding: 1px 6px;
|
|
120
|
+
font-size: 8px;
|
|
121
|
+
font-weight: 700;
|
|
122
|
+
letter-spacing: 0.05em;
|
|
123
|
+
text-transform: uppercase;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.split-pair-role-driver { color: var(--accent); background: color-mix(in srgb, var(--accent) 13%, transparent); }
|
|
127
|
+
.split-pair-role-worker { color: var(--text-secondary); background: var(--bg-alt); }
|
|
128
|
+
|
|
129
|
+
/* Role badges are buttons: Driver click clears roles, Worker click takes over. */
|
|
130
|
+
button.split-pair-role {
|
|
131
|
+
border: 0;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
font-family: inherit;
|
|
134
|
+
}
|
|
135
|
+
button.split-pair-role:hover { filter: brightness(1.25); }
|
|
136
|
+
|
|
137
|
+
/* Ad-hoc splits: role assignment in each pane header. ALWAYS fully
|
|
138
|
+
visible; hover/opacity gating made the control undiscoverable. */
|
|
139
|
+
.split-pair-set-driver {
|
|
140
|
+
flex-shrink: 0;
|
|
141
|
+
display: inline-flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
border: 1px solid var(--border);
|
|
144
|
+
border-radius: 999px;
|
|
145
|
+
padding: 2px 9px;
|
|
146
|
+
font-size: 9px;
|
|
147
|
+
font-weight: 700;
|
|
148
|
+
letter-spacing: 0.05em;
|
|
149
|
+
text-transform: uppercase;
|
|
150
|
+
background: var(--bg-alt);
|
|
151
|
+
color: var(--text-secondary);
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
154
|
+
font-family: inherit;
|
|
155
|
+
}
|
|
156
|
+
.split-pair-set-driver:hover {
|
|
157
|
+
color: var(--accent);
|
|
158
|
+
border-color: var(--accent);
|
|
159
|
+
background: color-mix(in srgb, var(--accent) 10%, var(--bg));
|
|
160
|
+
}
|
|
161
|
+
.split-pair-live { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); animation: pair-status-pulse 1.5s ease-in-out infinite; }
|
|
162
|
+
|
|
163
|
+
#split-host.split-delegating::after {
|
|
164
|
+
content: "→";
|
|
165
|
+
position: absolute;
|
|
166
|
+
z-index: 4;
|
|
167
|
+
top: calc(50% - 10px);
|
|
168
|
+
left: calc(50% - 17px);
|
|
169
|
+
width: 34px;
|
|
170
|
+
height: 20px;
|
|
171
|
+
display: grid;
|
|
172
|
+
place-items: center;
|
|
173
|
+
border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
|
|
174
|
+
border-radius: 999px;
|
|
175
|
+
pointer-events: none;
|
|
176
|
+
background: var(--sidebar-bg);
|
|
177
|
+
color: var(--accent);
|
|
178
|
+
font-size: 14px;
|
|
179
|
+
font-weight: 800;
|
|
180
|
+
animation: pair-divider-flow 1.1s ease-in-out infinite;
|
|
181
|
+
filter: drop-shadow(0 0 4px color-mix(in srgb, var(--accent) 65%, transparent));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#split-host.split-direction-left::after { content: "←"; animation-name: pair-divider-flow-left; }
|
|
185
|
+
|
|
186
|
+
@keyframes pair-divider-flow { 0%, 100% { transform: translateX(-3px); opacity: 0.65; } 50% { transform: translateX(3px); opacity: 1; } }
|
|
187
|
+
@keyframes pair-divider-flow-left { 0%, 100% { transform: translateX(3px); opacity: 0.65; } 50% { transform: translateX(-3px); opacity: 1; } }
|
|
188
|
+
@keyframes pair-status-pulse { 50% { opacity: 0.55; } }
|
|
189
|
+
|
|
190
|
+
/* Pair dialog rides the standard wt-modal pattern; these classes only add
|
|
191
|
+
the two-column role layout on top of it. */
|
|
192
|
+
.pair-modal { min-width: 480px; max-width: 560px; }
|
|
193
|
+
.pair-modal-desc { font-size: 12px; color: var(--text-dimmer); margin: -10px 0 14px; }
|
|
194
|
+
.pair-modal-roles { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
195
|
+
.pair-modal-role { min-width: 0; }
|
|
196
|
+
.pair-modal-role-heading { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
|
|
197
|
+
.pair-modal-role-heading div { display: grid; gap: 1px; }
|
|
198
|
+
.pair-modal-role-heading strong { color: var(--text); font-size: 12px; }
|
|
199
|
+
.pair-modal-role-heading span { color: var(--text-dimmer); font-size: 10px; }
|
|
200
|
+
@media (max-width: 560px) {
|
|
201
|
+
.pair-modal { min-width: 0; width: calc(100vw - 32px); }
|
|
202
|
+
.pair-modal-roles { grid-template-columns: 1fr; }
|
|
203
|
+
}
|
|
204
|
+
.pair-modal-current {
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: 8px;
|
|
208
|
+
padding: 8px 10px;
|
|
209
|
+
border: 1px solid var(--border-subtle);
|
|
210
|
+
border-radius: 8px;
|
|
211
|
+
background: var(--bg-alt);
|
|
212
|
+
font-size: 13px;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
overflow: hidden;
|
|
215
|
+
}
|
|
216
|
+
.pair-modal-current span {
|
|
217
|
+
overflow: hidden;
|
|
218
|
+
text-overflow: ellipsis;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
}
|
|
221
|
+
.pair-role-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--text-dimmer); box-shadow: 0 0 0 4px color-mix(in srgb, var(--text-dimmer) 12%, transparent); flex-shrink: 0; }
|
|
222
|
+
.pair-role-driver { background: var(--accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
223
|
+
|
|
224
|
+
.split-pane-rename-input {
|
|
225
|
+
flex: 1;
|
|
226
|
+
min-width: 0;
|
|
227
|
+
border: 1px solid var(--accent);
|
|
228
|
+
border-radius: 4px;
|
|
229
|
+
background: var(--bg);
|
|
230
|
+
color: var(--text);
|
|
231
|
+
font-size: 11px;
|
|
232
|
+
font-weight: 600;
|
|
233
|
+
padding: 1px 4px;
|
|
234
|
+
margin-left: -4px;
|
|
235
|
+
outline: none;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.split-pane-context {
|
|
239
|
+
display: none;
|
|
240
|
+
align-items: center;
|
|
241
|
+
gap: 4px;
|
|
242
|
+
border: 0;
|
|
243
|
+
padding: 2px 5px;
|
|
244
|
+
border-radius: 4px;
|
|
245
|
+
background: transparent;
|
|
246
|
+
color: var(--text-dimmer);
|
|
247
|
+
cursor: pointer;
|
|
248
|
+
flex-shrink: 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.split-pane-context.has-data {
|
|
252
|
+
display: inline-flex;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.split-pane-context:hover {
|
|
256
|
+
background: var(--bg-alt);
|
|
257
|
+
color: var(--text);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.split-pane-context-bar {
|
|
261
|
+
width: 36px;
|
|
262
|
+
height: 3px;
|
|
263
|
+
border-radius: 2px;
|
|
264
|
+
background: var(--border);
|
|
265
|
+
overflow: hidden;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.split-pane-context-fill {
|
|
269
|
+
display: block;
|
|
270
|
+
height: 100%;
|
|
271
|
+
width: 0%;
|
|
272
|
+
border-radius: 2px;
|
|
273
|
+
background: var(--accent);
|
|
274
|
+
transition: width 0.3s ease, background 0.3s ease;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.split-pane-context-fill.warn { background: var(--warning); }
|
|
278
|
+
.split-pane-context-fill.danger { background: var(--error); }
|
|
279
|
+
|
|
280
|
+
.split-pane-context-label {
|
|
281
|
+
font-family: "Roboto Mono", monospace;
|
|
282
|
+
font-size: 10px;
|
|
283
|
+
line-height: 1;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Header-scoped (two-class) selectors: pane.css loads BEFORE menus.css in
|
|
287
|
+
style.css, so the base .session-full-access rules would win a specificity
|
|
288
|
+
tie and keep the pill at its 22px title-bar size inside the 24px pane
|
|
289
|
+
header. Scope under .split-pane-header to outrank them. */
|
|
290
|
+
.split-pane-header .split-pane-full-access {
|
|
291
|
+
height: 15px;
|
|
292
|
+
align-self: center;
|
|
293
|
+
margin-left: 0;
|
|
294
|
+
padding: 0 3px 0 7px;
|
|
295
|
+
gap: 5px;
|
|
296
|
+
font-size: 8px;
|
|
297
|
+
letter-spacing: 0.02em;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.split-pane-header .session-full-access-track {
|
|
301
|
+
width: 16px;
|
|
302
|
+
height: 9px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.split-pane-header .session-full-access-track > span {
|
|
306
|
+
top: 1.5px;
|
|
307
|
+
left: 1.5px;
|
|
308
|
+
width: 6px;
|
|
309
|
+
height: 6px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.split-pane-header .split-pane-full-access.active .session-full-access-track > span { transform: translateX(7px); }
|
|
313
|
+
|
|
314
|
+
@media (max-width: 900px) {
|
|
315
|
+
.split-pane-header .session-full-access-label { display: none; }
|
|
316
|
+
.split-pane-header .split-pane-full-access { padding-left: 3px; }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.split-pane-close {
|
|
320
|
+
width: 20px;
|
|
321
|
+
height: 20px;
|
|
322
|
+
display: flex;
|
|
323
|
+
align-items: center;
|
|
324
|
+
justify-content: center;
|
|
325
|
+
border: 0;
|
|
326
|
+
border-radius: 4px;
|
|
327
|
+
background: transparent;
|
|
328
|
+
color: var(--text-dimmer);
|
|
329
|
+
cursor: pointer;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.split-pane-close:hover {
|
|
333
|
+
background: var(--bg-alt);
|
|
334
|
+
color: var(--text);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.split-pane-close svg {
|
|
338
|
+
width: 12px;
|
|
339
|
+
height: 12px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.split-pane-frame {
|
|
343
|
+
flex: 1;
|
|
344
|
+
width: 100%;
|
|
345
|
+
min-height: 0;
|
|
346
|
+
border: 0;
|
|
347
|
+
background: var(--bg);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Prevent an iframe from taking the pointer midway through a note gesture. */
|
|
351
|
+
body.sticky-note-interacting .split-pane-frame {
|
|
352
|
+
pointer-events: none;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Arc-style drop preview: while a session is dragged over the app, hovering
|
|
356
|
+
one half folds the live app into the other half and a ghost pane marks
|
|
357
|
+
exactly where the dragged session will land. Drop keeps that layout, so
|
|
358
|
+
the preview and the result match 1:1. */
|
|
359
|
+
|
|
360
|
+
.split-drop-overlay {
|
|
361
|
+
position: absolute;
|
|
362
|
+
inset: 0;
|
|
363
|
+
z-index: 10050;
|
|
364
|
+
display: none;
|
|
365
|
+
background: rgba(var(--overlay-rgb), 0.05);
|
|
366
|
+
pointer-events: none;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.split-drop-overlay.visible {
|
|
370
|
+
display: block;
|
|
371
|
+
pointer-events: auto;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#main-panels.split-drag-active #app {
|
|
375
|
+
transition: margin 0.18s ease;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
#main-panels.split-preview-left #app {
|
|
379
|
+
margin-left: 50%;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#main-panels.split-preview-right #app {
|
|
383
|
+
margin-right: 50%;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.split-drop-ghost {
|
|
387
|
+
position: absolute;
|
|
388
|
+
top: 10px;
|
|
389
|
+
bottom: 10px;
|
|
390
|
+
left: 10px;
|
|
391
|
+
width: calc(50% - 16px);
|
|
392
|
+
display: flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
justify-content: center;
|
|
395
|
+
border: 2px dashed var(--accent);
|
|
396
|
+
border-radius: 10px;
|
|
397
|
+
background: color-mix(in srgb, var(--accent) 10%, var(--bg));
|
|
398
|
+
color: var(--text);
|
|
399
|
+
opacity: 0;
|
|
400
|
+
pointer-events: none;
|
|
401
|
+
transition: left 0.18s ease, opacity 0.12s ease;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#main-panels.split-preview-left .split-drop-ghost {
|
|
405
|
+
opacity: 1;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
#main-panels.split-preview-right .split-drop-ghost {
|
|
409
|
+
left: calc(50% + 6px);
|
|
410
|
+
opacity: 1;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.split-drop-ghost-title {
|
|
414
|
+
max-width: 80%;
|
|
415
|
+
overflow: hidden;
|
|
416
|
+
text-overflow: ellipsis;
|
|
417
|
+
white-space: nowrap;
|
|
418
|
+
font-size: 13px;
|
|
419
|
+
font-weight: 700;
|
|
420
|
+
}
|
|
@@ -873,6 +873,46 @@
|
|
|
873
873
|
font-weight: 600;
|
|
874
874
|
}
|
|
875
875
|
|
|
876
|
+
.split-group-text {
|
|
877
|
+
display: flex;
|
|
878
|
+
align-items: center;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.split-group-glyph {
|
|
882
|
+
width: 14px;
|
|
883
|
+
margin-right: 4px;
|
|
884
|
+
display: inline-flex;
|
|
885
|
+
align-items: center;
|
|
886
|
+
color: var(--text-dimmer);
|
|
887
|
+
flex-shrink: 0;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.split-group-glyph svg {
|
|
891
|
+
width: 13px;
|
|
892
|
+
height: 13px;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.split-group-vendors {
|
|
896
|
+
display: inline-flex;
|
|
897
|
+
align-items: center;
|
|
898
|
+
margin-right: 6px;
|
|
899
|
+
flex-shrink: 0;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.split-group-vendors .session-vendor-icon {
|
|
903
|
+
margin-right: 0;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.split-group-vendors .session-vendor-icon + .session-vendor-icon {
|
|
907
|
+
margin-left: -3px;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.split-group-name {
|
|
911
|
+
overflow: hidden;
|
|
912
|
+
text-overflow: ellipsis;
|
|
913
|
+
white-space: nowrap;
|
|
914
|
+
}
|
|
915
|
+
|
|
876
916
|
.session-item.drag-over-above::before,
|
|
877
917
|
.session-item.drag-over-below::after {
|
|
878
918
|
content: "";
|
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
white-space: nowrap;
|
|
123
123
|
color: transparent;
|
|
124
124
|
pointer-events: auto;
|
|
125
|
+
touch-action: none;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
.sticky-note-spacer:active { cursor: grabbing; }
|
|
@@ -335,6 +336,7 @@
|
|
|
335
336
|
cursor: nwse-resize;
|
|
336
337
|
opacity: 0;
|
|
337
338
|
transition: opacity 0.15s;
|
|
339
|
+
touch-action: none;
|
|
338
340
|
}
|
|
339
341
|
|
|
340
342
|
.sticky-note:hover .sticky-note-resize { opacity: 0.3; }
|
package/lib/public/index.html
CHANGED
|
@@ -370,7 +370,11 @@
|
|
|
370
370
|
<button id="hamburger-btn" aria-label="Toggle sidebar" title="Toggle sidebar"><i data-lucide="menu"></i></button>
|
|
371
371
|
<span class="header-title" id="header-title">Connecting...</span>
|
|
372
372
|
<button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
|
|
373
|
+
<button id="header-full-access-btn" type="button" class="session-full-access hidden" role="switch" aria-checked="false" title="Skip Permissions is off — permission prompts are on">
|
|
374
|
+
<span class="session-full-access-label">Skip Permissions</span><span class="session-full-access-track" aria-hidden="true"><span></span></span>
|
|
375
|
+
</button>
|
|
373
376
|
<button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
|
|
377
|
+
<button id="header-add-worker-btn" type="button" title="Add Worker — this session becomes the Driver of a pair"><i data-lucide="user-plus"></i></button>
|
|
374
378
|
<button type="button" id="header-tui-close-btn" class="header-tui-close-btn hidden" title="Close terminal (keeps history, resume anytime)" aria-label="Close terminal"><i data-lucide="power"></i></button>
|
|
375
379
|
<div id="mate-mobile-title" class="mate-mobile-title hidden">
|
|
376
380
|
<img id="mate-mobile-avatar" class="mate-mobile-avatar" alt="">
|
|
@@ -144,6 +144,12 @@ export function connect() {
|
|
|
144
144
|
reconnectDelay = 1000;
|
|
145
145
|
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
146
146
|
|
|
147
|
+
// A pane pin is one-shot per WebSocket, not per page lifetime. The server
|
|
148
|
+
// intentionally does not restore pane presence after a daemon restart.
|
|
149
|
+
if (store.get('paneMode') && store.get('paneSessionId')) {
|
|
150
|
+
store.set({ panePinPending: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
147
153
|
// Wrap ws.send to blink LED on outgoing traffic
|
|
148
154
|
var currentWs = getWs();
|
|
149
155
|
var _origSend = currentWs.send.bind(currentWs);
|
|
@@ -18,6 +18,7 @@ var sessionIoTimers = {};
|
|
|
18
18
|
var crossProjectBlinkTimer = null;
|
|
19
19
|
|
|
20
20
|
export function initFavicon() {
|
|
21
|
+
if (store.get('paneMode')) return;
|
|
21
22
|
faviconLink = document.querySelector('link[rel="icon"]');
|
|
22
23
|
faviconCanvas = document.createElement("canvas");
|
|
23
24
|
faviconCanvas.width = 32;
|
|
@@ -38,6 +39,7 @@ export function initFavicon() {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function updateFavicon(bgColor) {
|
|
42
|
+
if (store.get('paneMode')) return;
|
|
41
43
|
if (!faviconLink) return;
|
|
42
44
|
if (!bgColor) {
|
|
43
45
|
if (faviconOrigHref) { faviconLink.href = faviconOrigHref; faviconOrigHref = null; }
|
|
@@ -59,6 +61,7 @@ export function updateFavicon(bgColor) {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export function drawFaviconAnimFrame() {
|
|
64
|
+
if (store.get('paneMode')) return;
|
|
62
65
|
if (!faviconImgReady) return;
|
|
63
66
|
var S = 32;
|
|
64
67
|
var bands = BAND_COLORS.length;
|
|
@@ -160,6 +163,7 @@ export function updateCrossProjectBlink() {
|
|
|
160
163
|
}
|
|
161
164
|
|
|
162
165
|
export function startUrgentBlink() {
|
|
166
|
+
if (store.get('paneMode')) return;
|
|
163
167
|
if (urgentBlinkTimer) return;
|
|
164
168
|
savedTitle = document.title;
|
|
165
169
|
if (!faviconOrigHref && faviconLink) faviconOrigHref = faviconLink.href;
|
|
@@ -175,6 +179,7 @@ export function startUrgentBlink() {
|
|
|
175
179
|
}
|
|
176
180
|
|
|
177
181
|
export function stopUrgentBlink() {
|
|
182
|
+
if (store.get('paneMode')) return;
|
|
178
183
|
if (!urgentBlinkTimer) return;
|
|
179
184
|
clearInterval(urgentBlinkTimer);
|
|
180
185
|
clearInterval(urgentTitleTimer);
|
|
@@ -11,6 +11,9 @@ import { processMessage } from './app-messages.js';
|
|
|
11
11
|
import { saveToolState, resetToolState, restoreToolState } from './tools.js';
|
|
12
12
|
import { getSessionUsage, setSessionUsage, getContextData, setContextData, updateContextPanel, updateUsagePanel } from './app-panels.js';
|
|
13
13
|
import { onHistoryPrepended as onSessionSearchHistoryPrepended } from './session-search.js';
|
|
14
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
15
|
+
import { openPairDialog } from './split-pair-ui.js';
|
|
16
|
+
import { showConfirm } from './app-misc.js';
|
|
14
17
|
|
|
15
18
|
// --- Module-owned state ---
|
|
16
19
|
var sessionInfoPopover = null;
|
|
@@ -20,6 +23,66 @@ export function initHeader() {
|
|
|
20
23
|
var headerRenameBtn = document.getElementById("header-rename-btn");
|
|
21
24
|
var headerTitleEl = document.getElementById("header-title");
|
|
22
25
|
var headerInfoBtn = document.getElementById("header-info-btn");
|
|
26
|
+
var headerAddWorkerBtn = document.getElementById("header-add-worker-btn");
|
|
27
|
+
|
|
28
|
+
// --- Add Worker: the current session becomes the Driver of a new pair ---
|
|
29
|
+
if (headerAddWorkerBtn) {
|
|
30
|
+
headerAddWorkerBtn.addEventListener("click", function () {
|
|
31
|
+
var sessionId = store.get('activeSessionId');
|
|
32
|
+
if (!sessionId || store.get('splitPanes') || store.get('dmMode')) return;
|
|
33
|
+
openPairDialog({
|
|
34
|
+
sessionId: sessionId,
|
|
35
|
+
title: headerTitleEl ? headerTitleEl.textContent : "",
|
|
36
|
+
vendor: store.get('currentVendor') || "claude",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
// A split shows the GROUP in the title bar; adding a worker there makes
|
|
40
|
+
// no sense (the group already has two members), so hide the button.
|
|
41
|
+
store.subscribe(function (state, prev) {
|
|
42
|
+
if (state.splitPanes !== prev.splitPanes) {
|
|
43
|
+
headerAddWorkerBtn.classList.toggle("hidden", !!state.splitPanes);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
var headerFullAccessBtn = document.getElementById("header-full-access-btn");
|
|
48
|
+
|
|
49
|
+
function updateFullAccessButton() {
|
|
50
|
+
if (!headerFullAccessBtn) return;
|
|
51
|
+
var s = store.snap();
|
|
52
|
+
var visible = !s.dmMode && !s.splitPanes && !s.skipPermsEnabled && s.activeSessionMode === "gui" && !!s.activeSessionId;
|
|
53
|
+
var enabled = !!s.sessionFullAccess;
|
|
54
|
+
headerFullAccessBtn.classList.toggle("hidden", !visible);
|
|
55
|
+
headerFullAccessBtn.classList.toggle("active", enabled);
|
|
56
|
+
headerFullAccessBtn.setAttribute("aria-checked", enabled ? "true" : "false");
|
|
57
|
+
headerFullAccessBtn.title = enabled ? "Skip Permissions is on — click to restore permission prompts" : "Skip Permissions is off — permission prompts are on";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sendFullAccess(enabled) {
|
|
61
|
+
var ws = getWs();
|
|
62
|
+
var id = store.get('activeSessionId');
|
|
63
|
+
if (ws && ws.readyState === 1 && id) {
|
|
64
|
+
ws.send(JSON.stringify({ type: "set_session_full_access", id: id, enabled: enabled }));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (headerFullAccessBtn) {
|
|
69
|
+
headerFullAccessBtn.addEventListener("click", function () {
|
|
70
|
+
if (store.get('sessionFullAccess')) {
|
|
71
|
+
sendFullAccess(false);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
showConfirm("Skip permission prompts for this session? Tool requests will be approved automatically.", function () {
|
|
75
|
+
sendFullAccess(true);
|
|
76
|
+
}, "Skip Permissions", true);
|
|
77
|
+
});
|
|
78
|
+
store.subscribe(function (state, prev) {
|
|
79
|
+
if (state.sessionFullAccess !== prev.sessionFullAccess || state.activeSessionId !== prev.activeSessionId ||
|
|
80
|
+
state.activeSessionMode !== prev.activeSessionMode ||
|
|
81
|
+
state.dmMode !== prev.dmMode || state.splitPanes !== prev.splitPanes ||
|
|
82
|
+
state.skipPermsEnabled !== prev.skipPermsEnabled) updateFullAccessButton();
|
|
83
|
+
});
|
|
84
|
+
updateFullAccessButton();
|
|
85
|
+
}
|
|
23
86
|
|
|
24
87
|
// --- Header session rename ---
|
|
25
88
|
if (headerRenameBtn) {
|
|
@@ -40,8 +103,18 @@ export function initHeader() {
|
|
|
40
103
|
var newTitle = input.value.trim();
|
|
41
104
|
var ws = getWs();
|
|
42
105
|
if (newTitle && newTitle !== currentText && ws && ws.readyState === 1) {
|
|
43
|
-
|
|
44
|
-
|
|
106
|
+
// With a split group open the header shows the GROUP name, so the
|
|
107
|
+
// pencil must rename the group, not the parent's last active session.
|
|
108
|
+
var split = store.get('splitPanes');
|
|
109
|
+
if (split) {
|
|
110
|
+
if (split.groupId) {
|
|
111
|
+
ws.send(JSON.stringify({ type: "split_group_rename", id: split.groupId, name: newTitle }));
|
|
112
|
+
headerTitleEl.textContent = newTitle;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
ws.send(JSON.stringify({ type: "rename_session", id: store.get('activeSessionId'), title: newTitle }));
|
|
116
|
+
headerTitleEl.textContent = newTitle;
|
|
117
|
+
}
|
|
45
118
|
}
|
|
46
119
|
input.remove();
|
|
47
120
|
headerTitleEl.style.display = "";
|
|
@@ -89,10 +162,32 @@ export function initHeader() {
|
|
|
89
162
|
pop.appendChild(row);
|
|
90
163
|
}
|
|
91
164
|
|
|
92
|
-
var
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
165
|
+
var split = store.get('splitPanes');
|
|
166
|
+
if (split && split.panes) {
|
|
167
|
+
// A split group is open: single-session info makes no sense here.
|
|
168
|
+
// Show one section per member instead.
|
|
169
|
+
var cached = getCachedSessions() || [];
|
|
170
|
+
for (var pi = 0; pi < split.panes.length; pi++) {
|
|
171
|
+
var member = null;
|
|
172
|
+
for (var ci = 0; ci < cached.length; ci++) {
|
|
173
|
+
if (cached[ci].id === split.panes[pi].sessionId) { member = cached[ci]; break; }
|
|
174
|
+
}
|
|
175
|
+
var heading = document.createElement("div");
|
|
176
|
+
heading.className = "info-section-title";
|
|
177
|
+
heading.textContent = (member && member.title) || split.panes[pi].title || ("Session " + split.panes[pi].sessionId);
|
|
178
|
+
pop.appendChild(heading);
|
|
179
|
+
addRow("Local ID", split.panes[pi].sessionId);
|
|
180
|
+
if (member && member.cliSessionId) {
|
|
181
|
+
addRow("Session ID", member.cliSessionId);
|
|
182
|
+
addRow("Resume", "claude --resume " + member.cliSessionId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
var s = store.snap();
|
|
187
|
+
if (s.cliSessionId) addRow("Session ID", s.cliSessionId);
|
|
188
|
+
if (s.activeSessionId) addRow("Local ID", s.activeSessionId);
|
|
189
|
+
if (s.cliSessionId) addRow("Resume", "claude --resume " + s.cliSessionId);
|
|
190
|
+
}
|
|
96
191
|
|
|
97
192
|
document.body.appendChild(pop);
|
|
98
193
|
sessionInfoPopover = pop;
|