clay-server 2.34.0-beta.8 → 2.34.0
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/project-connection.js +1 -9
- package/lib/project-sessions.js +38 -4
- package/lib/project-user-message.js +4 -3
- package/lib/project.js +12 -2
- package/lib/public/css/mobile-nav.css +0 -14
- package/lib/public/css/notifications-center.css +23 -19
- package/lib/public/css/sidebar.css +326 -101
- package/lib/public/index.html +7 -10
- package/lib/public/modules/diff.js +21 -7
- package/lib/public/modules/sidebar-mobile.js +10 -20
- package/lib/public/modules/sidebar-sessions.js +490 -113
- package/lib/public/modules/sidebar.js +8 -6
- package/lib/public/modules/tools.js +37 -10
- package/lib/public/sw.js +1 -1
- package/lib/sessions.js +90 -0
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/codex.js +31 -4
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { openSearch as openSessionSearch } from './session-search.js';
|
|
|
8
8
|
import { store } from './store.js';
|
|
9
9
|
import { getWs } from './ws-ref.js';
|
|
10
10
|
import { getSessionListEl } from './dom-refs.js';
|
|
11
|
-
import { dismissOverlayPanels, closeSidebar, updatePageTitle } from './sidebar.js';
|
|
11
|
+
import { dismissOverlayPanels, closeSidebar, updatePageTitle, spawnDustParticles } from './sidebar.js';
|
|
12
12
|
import { showConfirm } from './app-misc.js';
|
|
13
13
|
import { getUpcomingSchedules } from './scheduler.js';
|
|
14
14
|
import { refreshMobileChatSheet } from './sidebar-mobile.js';
|
|
@@ -32,6 +32,16 @@ var countdownContainer = null;
|
|
|
32
32
|
// --- Session context menu ---
|
|
33
33
|
var sessionCtxMenu = null;
|
|
34
34
|
var sessionCtxSessionId = null;
|
|
35
|
+
var draggedSessionId = null;
|
|
36
|
+
var draggedSessionBookmarked = false;
|
|
37
|
+
var openResumePickerModal = function () {};
|
|
38
|
+
var headerSearchOpen = false;
|
|
39
|
+
var armedDeleteSessionId = null;
|
|
40
|
+
var armedDeleteTimer = null;
|
|
41
|
+
|
|
42
|
+
export function openResumePicker() {
|
|
43
|
+
openResumePickerModal();
|
|
44
|
+
}
|
|
35
45
|
|
|
36
46
|
function sendSessionBookmark(sessionId, bookmarked) {
|
|
37
47
|
if (getWs() && store.get('connected')) {
|
|
@@ -40,76 +50,415 @@ function sendSessionBookmark(sessionId, bookmarked) {
|
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
function compareSessionListItems(a, b) {
|
|
43
|
-
var
|
|
44
|
-
var
|
|
53
|
+
var aData = a && a.type === "session" ? a.data : a;
|
|
54
|
+
var bData = b && b.type === "session" ? b.data : b;
|
|
55
|
+
var aBookmarked = !!(aData && aData.bookmarked);
|
|
56
|
+
var bBookmarked = !!(bData && bData.bookmarked);
|
|
45
57
|
if (aBookmarked !== bBookmarked) return aBookmarked ? -1 : 1;
|
|
58
|
+
if (aBookmarked && bBookmarked) {
|
|
59
|
+
var ao = aData && typeof aData.favoriteOrder === "number" ? aData.favoriteOrder : Number.MAX_SAFE_INTEGER;
|
|
60
|
+
var bo = bData && typeof bData.favoriteOrder === "number" ? bData.favoriteOrder : Number.MAX_SAFE_INTEGER;
|
|
61
|
+
if (ao !== bo) return ao - bo;
|
|
62
|
+
}
|
|
46
63
|
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
47
64
|
}
|
|
48
65
|
|
|
49
|
-
|
|
66
|
+
function clearSessionDragIndicators() {
|
|
67
|
+
var listEl = getSessionListEl();
|
|
68
|
+
if (!listEl) return;
|
|
69
|
+
var active = listEl.querySelectorAll(".session-favorites-divider.drag-hover, .session-regular-drop.drag-hover, .session-item.dragging");
|
|
70
|
+
for (var i = 0; i < active.length; i++) {
|
|
71
|
+
active[i].classList.remove("drag-hover", "dragging");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
50
74
|
|
|
51
|
-
|
|
75
|
+
function setupSessionDragHandlers(el, session) {
|
|
76
|
+
el.setAttribute("draggable", "true");
|
|
52
77
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
el.addEventListener("dragstart", function (e) {
|
|
79
|
+
draggedSessionId = session.id;
|
|
80
|
+
draggedSessionBookmarked = !!session.bookmarked;
|
|
81
|
+
e.dataTransfer.effectAllowed = "move";
|
|
82
|
+
e.dataTransfer.setData("text/plain", String(session.id));
|
|
83
|
+
|
|
84
|
+
var ghost = document.createElement("div");
|
|
85
|
+
ghost.textContent = session.title || "New Session";
|
|
86
|
+
ghost.style.cssText = "position:fixed;left:-200px;top:-200px;max-width:220px;padding:8px 12px;border-radius:10px;" +
|
|
87
|
+
"background:var(--sidebar-active);color:var(--text);font-size:13px;font-weight:600;pointer-events:none;z-index:-1;";
|
|
88
|
+
document.body.appendChild(ghost);
|
|
89
|
+
e.dataTransfer.setDragImage(ghost, 18, 18);
|
|
90
|
+
setTimeout(function () { ghost.remove(); }, 0);
|
|
91
|
+
|
|
92
|
+
setTimeout(function () { el.classList.add("dragging"); }, 0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
el.addEventListener("dragend", function () {
|
|
96
|
+
clearSessionDragIndicators();
|
|
97
|
+
draggedSessionId = null;
|
|
98
|
+
draggedSessionBookmarked = false;
|
|
99
|
+
});
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
101
|
+
if (session.bookmarked) {
|
|
102
|
+
el.addEventListener("dragover", function (e) {
|
|
103
|
+
if (!draggedSessionId || draggedSessionId === session.id) return;
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
e.dataTransfer.dropEffect = "move";
|
|
106
|
+
var rect = el.getBoundingClientRect();
|
|
107
|
+
var insertBefore = e.clientY < rect.top + rect.height / 2;
|
|
108
|
+
el.classList.remove("drag-over-above", "drag-over-below");
|
|
109
|
+
el.classList.add(insertBefore ? "drag-over-above" : "drag-over-below");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
el.addEventListener("dragleave", function () {
|
|
113
|
+
el.classList.remove("drag-over-above", "drag-over-below");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
el.addEventListener("drop", function (e) {
|
|
117
|
+
if (!draggedSessionId || draggedSessionId === session.id) return;
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
var rect = el.getBoundingClientRect();
|
|
120
|
+
var insertBefore = e.clientY < rect.top + rect.height / 2;
|
|
121
|
+
el.classList.remove("drag-over-above", "drag-over-below");
|
|
122
|
+
if (draggedSessionBookmarked) {
|
|
123
|
+
if (getWs() && store.get('connected')) {
|
|
124
|
+
getWs().send(JSON.stringify({
|
|
125
|
+
type: "reorder_session_bookmarks",
|
|
126
|
+
sourceId: draggedSessionId,
|
|
127
|
+
targetId: session.id,
|
|
128
|
+
insertBefore: insertBefore,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
sendSessionBookmark(draggedSessionId, true);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
65
135
|
}
|
|
136
|
+
}
|
|
66
137
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
138
|
+
function setupBookmarkDropTarget(el, bookmarked) {
|
|
139
|
+
el.addEventListener("dragover", function (e) {
|
|
140
|
+
if (!draggedSessionId) return;
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
e.dataTransfer.dropEffect = "move";
|
|
143
|
+
el.classList.add("drag-hover");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
el.addEventListener("dragleave", function () {
|
|
147
|
+
el.classList.remove("drag-hover");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
el.addEventListener("drop", function (e) {
|
|
151
|
+
if (!draggedSessionId) return;
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
el.classList.remove("drag-hover");
|
|
154
|
+
if (draggedSessionBookmarked !== !!bookmarked) {
|
|
155
|
+
sendSessionBookmark(draggedSessionId, !!bookmarked);
|
|
156
|
+
}
|
|
157
|
+
clearSessionDragIndicators();
|
|
158
|
+
draggedSessionId = null;
|
|
159
|
+
draggedSessionBookmarked = false;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function spawnSessionDeleteParticles(sessionId) {
|
|
164
|
+
if (!spawnDustParticles) return;
|
|
165
|
+
setTimeout(function () {
|
|
166
|
+
var el = getSessionListEl().querySelector('[data-session-id="' + sessionId + '"]');
|
|
167
|
+
if (!el) return;
|
|
168
|
+
var rect = el.getBoundingClientRect();
|
|
169
|
+
spawnDustParticles(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
170
|
+
}, 0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function confirmDeleteSession(session) {
|
|
174
|
+
showConfirm('Delete "' + (session.title || "New Session") + '"? This session and its history will be permanently removed.', function () {
|
|
175
|
+
var ws = getWs();
|
|
176
|
+
if (ws && store.get('connected')) {
|
|
177
|
+
ws.send(JSON.stringify({ type: "delete_session", id: session.id }));
|
|
178
|
+
spawnSessionDeleteParticles(session.id);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function clearArmedSessionDelete() {
|
|
184
|
+
if (armedDeleteTimer) {
|
|
185
|
+
clearTimeout(armedDeleteTimer);
|
|
186
|
+
armedDeleteTimer = null;
|
|
75
187
|
}
|
|
188
|
+
if (armedDeleteSessionId !== null) {
|
|
189
|
+
var prevBtn = getSessionListEl() ? getSessionListEl().querySelector('.session-close-btn[data-session-id="' + armedDeleteSessionId + '"]') : null;
|
|
190
|
+
if (prevBtn) {
|
|
191
|
+
prevBtn.classList.remove("armed");
|
|
192
|
+
prevBtn.innerHTML = iconHtml("x");
|
|
193
|
+
prevBtn.title = "Delete session";
|
|
194
|
+
prevBtn.setAttribute("aria-label", "Delete session");
|
|
195
|
+
refreshIcons();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
armedDeleteSessionId = null;
|
|
199
|
+
}
|
|
76
200
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
201
|
+
function armSessionDelete(closeBtn, session) {
|
|
202
|
+
clearArmedSessionDelete();
|
|
203
|
+
armedDeleteSessionId = session.id;
|
|
204
|
+
closeBtn.classList.add("armed");
|
|
205
|
+
closeBtn.innerHTML = iconHtml("check");
|
|
206
|
+
closeBtn.title = "Click again to delete";
|
|
207
|
+
closeBtn.setAttribute("aria-label", "Click again to delete");
|
|
208
|
+
refreshIcons();
|
|
209
|
+
armedDeleteTimer = setTimeout(function () {
|
|
210
|
+
clearArmedSessionDelete();
|
|
211
|
+
}, 1800);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function deleteSessionImmediately(session) {
|
|
215
|
+
var ws = getWs();
|
|
216
|
+
if (ws && store.get('connected')) {
|
|
217
|
+
ws.send(JSON.stringify({ type: "delete_session", id: session.id }));
|
|
218
|
+
spawnSessionDeleteParticles(session.id);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function collectItemSessionIds(item) {
|
|
223
|
+
if (!item) return [];
|
|
224
|
+
if (item.type === "session" && item.data && typeof item.data.id === "number") {
|
|
225
|
+
if (!isSessionVisibleBySearch(item.data.id)) return [];
|
|
226
|
+
return [item.data.id];
|
|
227
|
+
}
|
|
228
|
+
if (item.type === "loop" && Array.isArray(item.children)) {
|
|
229
|
+
var ids = [];
|
|
230
|
+
for (var i = 0; i < item.children.length; i++) {
|
|
231
|
+
if (typeof item.children[i].id === "number" && isSessionVisibleBySearch(item.children[i].id)) {
|
|
232
|
+
ids.push(item.children[i].id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return ids;
|
|
236
|
+
}
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function confirmDeleteSessionGroup(groupLabel, sessionIds) {
|
|
241
|
+
if (!Array.isArray(sessionIds) || sessionIds.length === 0) return;
|
|
242
|
+
var count = sessionIds.length;
|
|
243
|
+
var noun = count === 1 ? "session" : "sessions";
|
|
244
|
+
showConfirm('Clear "' + groupLabel + '"? ' + count + " " + noun + ' will be permanently removed.', function () {
|
|
245
|
+
var ws = getWs();
|
|
246
|
+
if (ws && store.get('connected')) {
|
|
247
|
+
ws.send(JSON.stringify({ type: "bulk_delete_sessions", sessionIds: sessionIds }));
|
|
82
248
|
}
|
|
83
249
|
});
|
|
250
|
+
}
|
|
84
251
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
252
|
+
function createSessionGroupHeader(group, sessionIds) {
|
|
253
|
+
var header = document.createElement("div");
|
|
254
|
+
header.className = "session-group-header";
|
|
255
|
+
|
|
256
|
+
var label = document.createElement("span");
|
|
257
|
+
label.className = "session-group-header-label";
|
|
258
|
+
label.textContent = group;
|
|
259
|
+
header.appendChild(label);
|
|
260
|
+
|
|
261
|
+
if ((!store.get('permissions') || store.get('permissions').sessionDelete !== false) && Array.isArray(sessionIds) && sessionIds.length > 0) {
|
|
262
|
+
var clearBtn = document.createElement("button");
|
|
263
|
+
clearBtn.className = "session-group-clear-btn";
|
|
264
|
+
clearBtn.type = "button";
|
|
265
|
+
clearBtn.textContent = "Clear";
|
|
266
|
+
clearBtn.addEventListener("click", function (e) {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
confirmDeleteSessionGroup(group, sessionIds);
|
|
88
270
|
});
|
|
271
|
+
header.appendChild(clearBtn);
|
|
89
272
|
}
|
|
90
273
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
274
|
+
return header;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function appendSessionCloseButton(el, session) {
|
|
278
|
+
if (store.get('permissions') && store.get('permissions').sessionDelete === false) return;
|
|
279
|
+
|
|
280
|
+
var closeBtn = document.createElement("button");
|
|
281
|
+
closeBtn.className = "session-close-btn";
|
|
282
|
+
closeBtn.dataset.sessionId = session.id;
|
|
283
|
+
closeBtn.type = "button";
|
|
284
|
+
closeBtn.title = "Delete session";
|
|
285
|
+
closeBtn.setAttribute("aria-label", "Delete session");
|
|
286
|
+
closeBtn.innerHTML = iconHtml("x");
|
|
287
|
+
closeBtn.addEventListener("click", function (e) {
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
e.stopPropagation();
|
|
290
|
+
if (armedDeleteSessionId === session.id) {
|
|
291
|
+
clearArmedSessionDelete();
|
|
292
|
+
deleteSessionImmediately(session);
|
|
97
293
|
return;
|
|
98
294
|
}
|
|
99
|
-
|
|
100
|
-
if (getWs() && store.get('connected')) {
|
|
101
|
-
getWs().send(JSON.stringify({ type: "search_sessions", query: searchQuery }));
|
|
102
|
-
}
|
|
103
|
-
}, 200);
|
|
295
|
+
armSessionDelete(closeBtn, session);
|
|
104
296
|
});
|
|
297
|
+
el.appendChild(closeBtn);
|
|
298
|
+
}
|
|
105
299
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
300
|
+
function renderSessionTopActions() {
|
|
301
|
+
var wrap = document.createElement("div");
|
|
302
|
+
wrap.className = "session-top-actions";
|
|
303
|
+
|
|
304
|
+
var newBtn = document.createElement("button");
|
|
305
|
+
newBtn.className = "session-top-action";
|
|
306
|
+
newBtn.type = "button";
|
|
307
|
+
newBtn.innerHTML = iconHtml("plus") + '<span>New Session</span>';
|
|
308
|
+
newBtn.addEventListener("click", function () {
|
|
309
|
+
if (getWs() && store.get('connected')) {
|
|
310
|
+
getWs().send(JSON.stringify({ type: "new_session" }));
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
wrap.appendChild(newBtn);
|
|
314
|
+
|
|
315
|
+
var importBtn = document.createElement("button");
|
|
316
|
+
importBtn.className = "session-top-action";
|
|
317
|
+
importBtn.type = "button";
|
|
318
|
+
importBtn.innerHTML = iconHtml("import") + '<span>Import CLI</span>';
|
|
319
|
+
importBtn.addEventListener("click", function () {
|
|
320
|
+
openResumePickerModal();
|
|
321
|
+
});
|
|
322
|
+
wrap.appendChild(importBtn);
|
|
323
|
+
|
|
324
|
+
return wrap;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function runSessionSearch(query) {
|
|
328
|
+
var normalizedQuery = query || "";
|
|
329
|
+
var trimmedQuery = normalizedQuery.trim();
|
|
330
|
+
searchQuery = normalizedQuery;
|
|
331
|
+
if (searchDebounce) {
|
|
332
|
+
clearTimeout(searchDebounce);
|
|
333
|
+
searchDebounce = null;
|
|
334
|
+
}
|
|
335
|
+
if (!trimmedQuery) {
|
|
336
|
+
searchMatchIds = null;
|
|
337
|
+
renderSessionList(null);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
searchDebounce = setTimeout(function () {
|
|
341
|
+
if (getWs() && store.get('connected')) {
|
|
342
|
+
getWs().send(JSON.stringify({ type: "search_sessions", query: searchQuery }));
|
|
110
343
|
}
|
|
344
|
+
}, 200);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function syncHeaderSearchUi() {
|
|
348
|
+
var searchInline = document.getElementById("session-header-search-inline");
|
|
349
|
+
var searchInput = document.getElementById("session-header-search-input");
|
|
350
|
+
var searchClear = document.getElementById("session-header-search-clear");
|
|
351
|
+
var searchBtn = document.getElementById("session-header-search-btn");
|
|
352
|
+
var filterCount = document.getElementById("session-filter-count");
|
|
353
|
+
var isOpen = headerSearchOpen || !!searchQuery;
|
|
354
|
+
if (!searchInline || !searchInput || !searchClear || !searchBtn || !filterCount) return;
|
|
355
|
+
searchInline.classList.toggle("hidden", !isOpen);
|
|
356
|
+
searchBtn.classList.toggle("active", isOpen);
|
|
357
|
+
if (searchInput.value !== searchQuery) {
|
|
358
|
+
searchInput.value = searchQuery;
|
|
359
|
+
}
|
|
360
|
+
searchClear.classList.toggle("hidden", !searchQuery);
|
|
361
|
+
if (!searchQuery || searchMatchIds === null) {
|
|
362
|
+
filterCount.classList.add("hidden");
|
|
363
|
+
filterCount.textContent = "";
|
|
364
|
+
} else {
|
|
365
|
+
filterCount.classList.remove("hidden");
|
|
366
|
+
filterCount.textContent = String(searchMatchIds.size);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function openHeaderSearch() {
|
|
371
|
+
headerSearchOpen = true;
|
|
372
|
+
syncHeaderSearchUi();
|
|
373
|
+
var searchInput = document.getElementById("session-header-search-input");
|
|
374
|
+
if (searchInput) {
|
|
375
|
+
requestAnimationFrame(function () {
|
|
376
|
+
searchInput.focus();
|
|
377
|
+
searchInput.select();
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function closeHeaderSearch() {
|
|
383
|
+
headerSearchOpen = false;
|
|
384
|
+
syncHeaderSearchUi();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function clearSessionSearch(shouldBlur, input, shouldClose) {
|
|
388
|
+
if (searchDebounce) {
|
|
389
|
+
clearTimeout(searchDebounce);
|
|
390
|
+
searchDebounce = null;
|
|
391
|
+
}
|
|
392
|
+
searchQuery = "";
|
|
393
|
+
searchMatchIds = null;
|
|
394
|
+
if (shouldClose) {
|
|
395
|
+
headerSearchOpen = false;
|
|
396
|
+
}
|
|
397
|
+
syncHeaderSearchUi();
|
|
398
|
+
renderSessionList(null);
|
|
399
|
+
if (shouldBlur && input) {
|
|
400
|
+
input.blur();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function initSidebarSessions() {
|
|
405
|
+
|
|
406
|
+
document.addEventListener("click", function () {
|
|
407
|
+
closeSessionCtxMenu();
|
|
408
|
+
clearArmedSessionDelete();
|
|
111
409
|
});
|
|
112
410
|
|
|
411
|
+
var searchBtn = document.getElementById("session-header-search-btn");
|
|
412
|
+
var searchInput = document.getElementById("session-header-search-input");
|
|
413
|
+
var searchClear = document.getElementById("session-header-search-clear");
|
|
414
|
+
var searchInline = document.getElementById("session-header-search-inline");
|
|
415
|
+
|
|
416
|
+
if (searchBtn && searchInput && searchClear && searchInline) {
|
|
417
|
+
searchBtn.addEventListener("click", function () {
|
|
418
|
+
if (!headerSearchOpen && !searchQuery) {
|
|
419
|
+
openHeaderSearch();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (!searchQuery) {
|
|
423
|
+
closeHeaderSearch();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
searchInput.focus();
|
|
427
|
+
searchInput.select();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
searchInput.addEventListener("input", function () {
|
|
431
|
+
runSessionSearch(searchInput.value);
|
|
432
|
+
syncHeaderSearchUi();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
searchInput.addEventListener("keydown", function (e) {
|
|
436
|
+
if (e.key === "Escape") {
|
|
437
|
+
e.preventDefault();
|
|
438
|
+
if (searchInput.value.trim()) {
|
|
439
|
+
clearSessionSearch(false, searchInput, false);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
clearSessionSearch(true, searchInput, true);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
searchInput.addEventListener("blur", function () {
|
|
447
|
+
setTimeout(function () {
|
|
448
|
+
if (!searchQuery && document.activeElement !== searchBtn && document.activeElement !== searchClear) {
|
|
449
|
+
closeHeaderSearch();
|
|
450
|
+
}
|
|
451
|
+
}, 0);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
searchClear.addEventListener("click", function () {
|
|
455
|
+
clearSessionSearch(false, searchInput, false);
|
|
456
|
+
searchInput.focus();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
syncHeaderSearchUi();
|
|
460
|
+
}
|
|
461
|
+
|
|
113
462
|
// --- Resume session picker ---
|
|
114
463
|
var resumeModal = document.getElementById("resume-modal");
|
|
115
464
|
var resumeCancel = document.getElementById("resume-cancel");
|
|
@@ -127,13 +476,12 @@ export function initSidebarSessions() {
|
|
|
127
476
|
getWs().send(JSON.stringify({ type: "list_cli_sessions" }));
|
|
128
477
|
}
|
|
129
478
|
}
|
|
479
|
+
openResumePickerModal = openResumeModal;
|
|
130
480
|
|
|
131
481
|
function closeResumeModal() {
|
|
132
482
|
resumeModal.classList.add("hidden");
|
|
133
483
|
}
|
|
134
484
|
|
|
135
|
-
var resumeBtn = document.getElementById("resume-session-btn");
|
|
136
|
-
if (resumeBtn) resumeBtn.addEventListener("click", openResumeModal);
|
|
137
485
|
resumeCancel.addEventListener("click", closeResumeModal);
|
|
138
486
|
resumeModal.querySelector(".confirm-backdrop").addEventListener("click", closeResumeModal);
|
|
139
487
|
|
|
@@ -182,7 +530,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
182
530
|
|
|
183
531
|
var bookmarkItem = document.createElement("button");
|
|
184
532
|
bookmarkItem.className = "session-ctx-item";
|
|
185
|
-
bookmarkItem.innerHTML = iconHtml(sessionData && sessionData.bookmarked ? "
|
|
533
|
+
bookmarkItem.innerHTML = iconHtml(sessionData && sessionData.bookmarked ? "arrow-down" : "arrow-up") + " <span>" + (sessionData && sessionData.bookmarked ? "Remove from Favorites" : "Add to Favorites") + "</span>";
|
|
186
534
|
bookmarkItem.addEventListener("click", function (e) {
|
|
187
535
|
e.stopPropagation();
|
|
188
536
|
closeSessionCtxMenu();
|
|
@@ -225,12 +573,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
225
573
|
deleteItem.addEventListener("click", function (e) {
|
|
226
574
|
e.stopPropagation();
|
|
227
575
|
closeSessionCtxMenu();
|
|
228
|
-
|
|
229
|
-
var ws = getWs();
|
|
230
|
-
if (ws && store.get('connected')) {
|
|
231
|
-
ws.send(JSON.stringify({ type: "delete_session", id: sessionId }));
|
|
232
|
-
}
|
|
233
|
-
});
|
|
576
|
+
confirmDeleteSession({ id: sessionId, title: title });
|
|
234
577
|
});
|
|
235
578
|
menu.appendChild(deleteItem);
|
|
236
579
|
}
|
|
@@ -409,13 +752,17 @@ export function highlightMatch(text, query) {
|
|
|
409
752
|
return escapeHtml(before) + '<mark class="session-highlight">' + escapeHtml(match) + '</mark>' + escapeHtml(after);
|
|
410
753
|
}
|
|
411
754
|
|
|
755
|
+
function isSessionVisibleBySearch(sessionId) {
|
|
756
|
+
if (searchMatchIds === null) return true;
|
|
757
|
+
return searchMatchIds.has(sessionId);
|
|
758
|
+
}
|
|
759
|
+
|
|
412
760
|
// --- Loop child / run / group rendering ---
|
|
413
761
|
|
|
414
762
|
function renderLoopChild(s) {
|
|
415
763
|
var el = document.createElement("div");
|
|
416
764
|
var isMatch = searchMatchIds !== null && searchMatchIds.has(s.id);
|
|
417
|
-
|
|
418
|
-
el.className = "session-loop-child" + (s.active ? " active" : "") + (isMatch ? " search-match" : "") + (dimmed ? " search-dimmed" : "");
|
|
765
|
+
el.className = "session-loop-child" + (s.active ? " active" : "") + (isMatch ? " search-match" : "");
|
|
419
766
|
el.dataset.sessionId = s.id;
|
|
420
767
|
|
|
421
768
|
var textSpan = document.createElement("span");
|
|
@@ -433,6 +780,7 @@ function renderLoopChild(s) {
|
|
|
433
780
|
}
|
|
434
781
|
textSpan.innerHTML = textHtml;
|
|
435
782
|
el.appendChild(textSpan);
|
|
783
|
+
appendSessionCloseButton(el, s);
|
|
436
784
|
|
|
437
785
|
el.addEventListener("click", (function (id) {
|
|
438
786
|
return function () {
|
|
@@ -448,14 +796,27 @@ function renderLoopChild(s) {
|
|
|
448
796
|
}
|
|
449
797
|
|
|
450
798
|
function renderLoopGroup(loopId, children, groupKey) {
|
|
799
|
+
var visibleChildren = children;
|
|
800
|
+
if (searchMatchIds !== null) {
|
|
801
|
+
visibleChildren = [];
|
|
802
|
+
for (var vi = 0; vi < children.length; vi++) {
|
|
803
|
+
if (isSessionVisibleBySearch(children[vi].id)) {
|
|
804
|
+
visibleChildren.push(children[vi]);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (visibleChildren.length === 0) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
451
812
|
var gk = groupKey || loopId;
|
|
452
813
|
|
|
453
814
|
// Sub-group children by startedAt (each run)
|
|
454
815
|
var runMap = {};
|
|
455
|
-
for (var i = 0; i <
|
|
456
|
-
var runKey = String(
|
|
816
|
+
for (var i = 0; i < visibleChildren.length; i++) {
|
|
817
|
+
var runKey = String(visibleChildren[i].loop && visibleChildren[i].loop.startedAt || 0);
|
|
457
818
|
if (!runMap[runKey]) runMap[runKey] = [];
|
|
458
|
-
runMap[runKey].push(
|
|
819
|
+
runMap[runKey].push(visibleChildren[i]);
|
|
459
820
|
}
|
|
460
821
|
var runKeys = Object.keys(runMap);
|
|
461
822
|
|
|
@@ -477,21 +838,21 @@ function renderLoopGroup(loopId, children, groupKey) {
|
|
|
477
838
|
var expanded = expandedLoopGroups.has(gk);
|
|
478
839
|
var hasActive = false;
|
|
479
840
|
var anyProcessing = false;
|
|
480
|
-
var latestSession =
|
|
481
|
-
for (var ci = 0; ci <
|
|
482
|
-
if (
|
|
483
|
-
if (
|
|
484
|
-
if ((
|
|
485
|
-
latestSession =
|
|
841
|
+
var latestSession = visibleChildren[0];
|
|
842
|
+
for (var ci = 0; ci < visibleChildren.length; ci++) {
|
|
843
|
+
if (visibleChildren[ci].active) hasActive = true;
|
|
844
|
+
if (visibleChildren[ci].isProcessing) anyProcessing = true;
|
|
845
|
+
if ((visibleChildren[ci].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
846
|
+
latestSession = visibleChildren[ci];
|
|
486
847
|
}
|
|
487
848
|
}
|
|
488
849
|
|
|
489
|
-
var loopName = (
|
|
490
|
-
var isRalph =
|
|
491
|
-
var isDebate =
|
|
850
|
+
var loopName = (visibleChildren[0].loop && visibleChildren[0].loop.name) || "Loop";
|
|
851
|
+
var isRalph = visibleChildren[0].loop && visibleChildren[0].loop.source === "ralph";
|
|
852
|
+
var isDebate = visibleChildren[0].loop && visibleChildren[0].loop.source === "debate";
|
|
492
853
|
var isCrafting = false;
|
|
493
|
-
for (var j = 0; j <
|
|
494
|
-
if (
|
|
854
|
+
for (var j = 0; j < visibleChildren.length; j++) {
|
|
855
|
+
if (visibleChildren[j].loop && visibleChildren[j].loop.role === "crafting") isCrafting = true;
|
|
495
856
|
}
|
|
496
857
|
|
|
497
858
|
var runCount = runKeys.length;
|
|
@@ -536,7 +897,7 @@ function renderLoopGroup(loopId, children, groupKey) {
|
|
|
536
897
|
if (isCrafting && children.length === 1) {
|
|
537
898
|
textHtml += '<span class="session-loop-badge crafting">Crafting</span>';
|
|
538
899
|
} else {
|
|
539
|
-
var countLabel = runCount === 1 ?
|
|
900
|
+
var countLabel = runCount === 1 ? visibleChildren.length : runCount + (runCount === 1 ? " run" : " runs");
|
|
540
901
|
var countClass = isDebate ? " debate" : (isRalph ? "" : " scheduled");
|
|
541
902
|
textHtml += '<span class="session-loop-count' + countClass + '">' + countLabel + '</span>';
|
|
542
903
|
}
|
|
@@ -553,7 +914,7 @@ function renderLoopGroup(loopId, children, groupKey) {
|
|
|
553
914
|
e.stopPropagation();
|
|
554
915
|
showLoopCtxMenu(btn, lid, name, count);
|
|
555
916
|
};
|
|
556
|
-
})(loopId, loopName,
|
|
917
|
+
})(loopId, loopName, visibleChildren.length, moreBtn));
|
|
557
918
|
el.appendChild(moreBtn);
|
|
558
919
|
|
|
559
920
|
// Click row (not chevron/more) -> switch to latest session
|
|
@@ -673,27 +1034,9 @@ function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
|
|
|
673
1034
|
function renderSessionItem(s) {
|
|
674
1035
|
var el = document.createElement("div");
|
|
675
1036
|
var isMatch = searchMatchIds !== null && searchMatchIds.has(s.id);
|
|
676
|
-
|
|
677
|
-
el.className = "session-item" + (s.active ? " active" : "") + (isMatch ? " search-match" : "") + (dimmed ? " search-dimmed" : "");
|
|
1037
|
+
el.className = "session-item" + (s.active ? " active" : "") + (isMatch ? " search-match" : "");
|
|
678
1038
|
el.dataset.sessionId = s.id;
|
|
679
1039
|
|
|
680
|
-
var bookmarkBtn = document.createElement("button");
|
|
681
|
-
bookmarkBtn.className = "session-bookmark-btn" + (s.bookmarked ? " bookmarked inline" : " hover");
|
|
682
|
-
bookmarkBtn.type = "button";
|
|
683
|
-
bookmarkBtn.title = s.bookmarked ? "Remove bookmark" : "Bookmark";
|
|
684
|
-
bookmarkBtn.setAttribute("aria-label", s.bookmarked ? "Remove bookmark" : "Bookmark");
|
|
685
|
-
bookmarkBtn.innerHTML = iconHtml("star");
|
|
686
|
-
bookmarkBtn.addEventListener("click", (function (id, bookmarked) {
|
|
687
|
-
return function (e) {
|
|
688
|
-
e.preventDefault();
|
|
689
|
-
e.stopPropagation();
|
|
690
|
-
sendSessionBookmark(id, !bookmarked);
|
|
691
|
-
};
|
|
692
|
-
})(s.id, !!s.bookmarked));
|
|
693
|
-
if (s.bookmarked) {
|
|
694
|
-
el.appendChild(bookmarkBtn);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
1040
|
var textSpan = document.createElement("span");
|
|
698
1041
|
textSpan.className = "session-item-text";
|
|
699
1042
|
var textHtml = "";
|
|
@@ -728,10 +1071,7 @@ function renderSessionItem(s) {
|
|
|
728
1071
|
unreadBadge.classList.add("has-unread");
|
|
729
1072
|
}
|
|
730
1073
|
el.appendChild(unreadBadge);
|
|
731
|
-
|
|
732
|
-
if (!s.bookmarked) {
|
|
733
|
-
el.appendChild(bookmarkBtn);
|
|
734
|
-
}
|
|
1074
|
+
appendSessionCloseButton(el, s);
|
|
735
1075
|
|
|
736
1076
|
el.addEventListener("click", (function (id) {
|
|
737
1077
|
return function () {
|
|
@@ -749,6 +1089,7 @@ function renderSessionItem(s) {
|
|
|
749
1089
|
|
|
750
1090
|
// Presence avatars (multi-user)
|
|
751
1091
|
renderPresenceAvatars(el, String(s.id));
|
|
1092
|
+
setupSessionDragHandlers(el, s);
|
|
752
1093
|
|
|
753
1094
|
return el;
|
|
754
1095
|
}
|
|
@@ -808,6 +1149,9 @@ export function renderSessionList(sessions) {
|
|
|
808
1149
|
var regularItems = [];
|
|
809
1150
|
for (var n = 0; n < items.length; n++) {
|
|
810
1151
|
var item = items[n];
|
|
1152
|
+
if (item.type === "session" && item.data && !isSessionVisibleBySearch(item.data.id)) {
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
811
1155
|
if (item.type === "session" && item.data && item.data.bookmarked) {
|
|
812
1156
|
bookmarkedItems.push(item);
|
|
813
1157
|
} else {
|
|
@@ -815,36 +1159,62 @@ export function renderSessionList(sessions) {
|
|
|
815
1159
|
}
|
|
816
1160
|
}
|
|
817
1161
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1162
|
+
var favoritesContainer = document.createElement("div");
|
|
1163
|
+
favoritesContainer.className = "session-favorites-section";
|
|
1164
|
+
setupBookmarkDropTarget(favoritesContainer, true);
|
|
1165
|
+
if (bookmarkedItems.length === 0) {
|
|
1166
|
+
var emptyHint = document.createElement("div");
|
|
1167
|
+
emptyHint.className = "session-favorites-empty";
|
|
1168
|
+
emptyHint.textContent = "Drag and drop sessions here to add favorites.";
|
|
1169
|
+
favoritesContainer.appendChild(emptyHint);
|
|
1170
|
+
}
|
|
1171
|
+
for (var bi = 0; bi < bookmarkedItems.length; bi++) {
|
|
1172
|
+
favoritesContainer.appendChild(renderSessionItem(bookmarkedItems[bi].data));
|
|
827
1173
|
}
|
|
828
1174
|
|
|
1175
|
+
var divider = document.createElement("div");
|
|
1176
|
+
divider.className = "session-favorites-divider";
|
|
1177
|
+
|
|
1178
|
+
var regularContainer = document.createElement("div");
|
|
1179
|
+
regularContainer.className = "session-regular-drop";
|
|
1180
|
+
setupBookmarkDropTarget(regularContainer, false);
|
|
1181
|
+
var stickyTop = document.createElement("div");
|
|
1182
|
+
stickyTop.className = "session-list-sticky-top";
|
|
1183
|
+
stickyTop.appendChild(favoritesContainer);
|
|
1184
|
+
stickyTop.appendChild(divider);
|
|
1185
|
+
stickyTop.appendChild(renderSessionTopActions());
|
|
1186
|
+
getSessionListEl().appendChild(stickyTop);
|
|
1187
|
+
|
|
829
1188
|
var currentGroup = "";
|
|
1189
|
+
var currentGroupIds = [];
|
|
830
1190
|
for (var ri = 0; ri < regularItems.length; ri++) {
|
|
831
1191
|
var item = regularItems[ri];
|
|
832
1192
|
var group = getDateGroup(item.lastActivity || 0);
|
|
833
1193
|
if (group !== currentGroup) {
|
|
834
1194
|
currentGroup = group;
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
1195
|
+
currentGroupIds = [];
|
|
1196
|
+
for (var gi = ri; gi < regularItems.length; gi++) {
|
|
1197
|
+
if (getDateGroup(regularItems[gi].lastActivity || 0) !== group) break;
|
|
1198
|
+
var groupIds = collectItemSessionIds(regularItems[gi]);
|
|
1199
|
+
for (var gj = 0; gj < groupIds.length; gj++) currentGroupIds.push(groupIds[gj]);
|
|
1200
|
+
}
|
|
1201
|
+
if (group !== "Today") {
|
|
1202
|
+
regularContainer.appendChild(createSessionGroupHeader(group, currentGroupIds));
|
|
1203
|
+
}
|
|
839
1204
|
}
|
|
840
1205
|
if (item.type === "loop") {
|
|
841
|
-
|
|
1206
|
+
var loopEl = renderLoopGroup(item.loopId, item.children, item.groupKey);
|
|
1207
|
+
if (loopEl) {
|
|
1208
|
+
regularContainer.appendChild(loopEl);
|
|
1209
|
+
}
|
|
842
1210
|
} else {
|
|
843
|
-
|
|
1211
|
+
regularContainer.appendChild(renderSessionItem(item.data));
|
|
844
1212
|
}
|
|
845
1213
|
}
|
|
1214
|
+
getSessionListEl().appendChild(regularContainer);
|
|
846
1215
|
refreshIcons();
|
|
847
1216
|
if (updatePageTitle) updatePageTitle();
|
|
1217
|
+
syncHeaderSearchUi();
|
|
848
1218
|
}
|
|
849
1219
|
|
|
850
1220
|
// --- Search results ---
|
|
@@ -955,7 +1325,14 @@ function updateCountdowns() {
|
|
|
955
1325
|
if (!countdownContainer) {
|
|
956
1326
|
countdownContainer = document.createElement("div");
|
|
957
1327
|
countdownContainer.className = "session-countdown-group";
|
|
958
|
-
getSessionListEl().
|
|
1328
|
+
var stickyTop = getSessionListEl().querySelector(".session-list-sticky-top");
|
|
1329
|
+
if (stickyTop && stickyTop.nextSibling) {
|
|
1330
|
+
getSessionListEl().insertBefore(countdownContainer, stickyTop.nextSibling);
|
|
1331
|
+
} else if (stickyTop) {
|
|
1332
|
+
getSessionListEl().appendChild(countdownContainer);
|
|
1333
|
+
} else {
|
|
1334
|
+
getSessionListEl().insertBefore(countdownContainer, getSessionListEl().firstChild);
|
|
1335
|
+
}
|
|
959
1336
|
}
|
|
960
1337
|
|
|
961
1338
|
var html = "";
|