clay-server 2.7.2 → 2.8.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/bin/cli.js +2 -1
- package/lib/config.js +7 -4
- package/lib/project.js +343 -15
- package/lib/public/app.js +1043 -135
- package/lib/public/apple-touch-icon-dark.png +0 -0
- package/lib/public/apple-touch-icon.png +0 -0
- package/lib/public/clay-logo.png +0 -0
- package/lib/public/css/base.css +10 -0
- package/lib/public/css/filebrowser.css +1 -0
- package/lib/public/css/home-hub.css +455 -0
- package/lib/public/css/icon-strip.css +6 -5
- package/lib/public/css/loop.css +141 -23
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/mobile-nav.css +38 -12
- package/lib/public/css/overlays.css +205 -169
- package/lib/public/css/playbook.css +264 -0
- package/lib/public/css/profile.css +268 -0
- package/lib/public/css/scheduler-modal.css +1429 -0
- package/lib/public/css/scheduler.css +1305 -0
- package/lib/public/css/sidebar.css +305 -11
- package/lib/public/css/sticky-notes.css +23 -19
- package/lib/public/css/stt.css +155 -0
- package/lib/public/css/title-bar.css +14 -6
- package/lib/public/favicon-banded-32.png +0 -0
- package/lib/public/favicon-banded.png +0 -0
- package/lib/public/icon-192-dark.png +0 -0
- package/lib/public/icon-192.png +0 -0
- package/lib/public/icon-512-dark.png +0 -0
- package/lib/public/icon-512.png +0 -0
- package/lib/public/icon-banded-76.png +0 -0
- package/lib/public/icon-banded-96.png +0 -0
- package/lib/public/index.html +335 -42
- package/lib/public/modules/ascii-logo.js +389 -0
- package/lib/public/modules/filebrowser.js +2 -1
- package/lib/public/modules/markdown.js +118 -0
- package/lib/public/modules/notifications.js +50 -63
- package/lib/public/modules/playbook.js +578 -0
- package/lib/public/modules/profile.js +357 -0
- package/lib/public/modules/project-settings.js +4 -9
- package/lib/public/modules/scheduler.js +2826 -0
- package/lib/public/modules/server-settings.js +1 -1
- package/lib/public/modules/sidebar.js +378 -31
- package/lib/public/modules/sticky-notes.js +2 -0
- package/lib/public/modules/stt.js +272 -0
- package/lib/public/modules/terminal.js +32 -0
- package/lib/public/modules/theme.js +3 -10
- package/lib/public/modules/tools.js +2 -1
- package/lib/public/style.css +6 -0
- package/lib/public/sw.js +82 -3
- package/lib/public/wordmark-banded-20.png +0 -0
- package/lib/public/wordmark-banded-32.png +0 -0
- package/lib/public/wordmark-banded-64.png +0 -0
- package/lib/public/wordmark-banded-80.png +0 -0
- package/lib/scheduler.js +402 -0
- package/lib/sdk-bridge.js +3 -2
- package/lib/server.js +124 -3
- package/lib/sessions.js +35 -2
- package/package.json +1 -1
|
@@ -316,7 +316,7 @@ function populateSettings() {
|
|
|
316
316
|
if (wsPathEl) wsPathEl.textContent = ctx.wsPath || "/ws";
|
|
317
317
|
|
|
318
318
|
// Skip permissions
|
|
319
|
-
var spBanner = document.getElementById("skip-perms-
|
|
319
|
+
var spBanner = document.getElementById("skip-perms-pill");
|
|
320
320
|
if (skipPermsEl) {
|
|
321
321
|
var isSkip = spBanner && !spBanner.classList.contains("hidden");
|
|
322
322
|
skipPermsEl.textContent = isSkip ? "Enabled" : "Disabled";
|
|
@@ -2,6 +2,7 @@ import { escapeHtml, copyToClipboard } from './utils.js';
|
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
import { openProjectSettings } from './project-settings.js';
|
|
4
4
|
import { triggerShare } from './qrcode.js';
|
|
5
|
+
import { parseEmojis } from './markdown.js';
|
|
5
6
|
|
|
6
7
|
var ctx;
|
|
7
8
|
|
|
@@ -10,11 +11,16 @@ var searchQuery = "";
|
|
|
10
11
|
var searchMatchIds = null; // null = no search, Set of matched session IDs
|
|
11
12
|
var searchDebounce = null;
|
|
12
13
|
var cachedSessions = [];
|
|
14
|
+
var expandedLoopGroups = new Set();
|
|
13
15
|
|
|
14
16
|
// --- Cached project data for mobile sheet ---
|
|
15
17
|
var cachedProjectList = [];
|
|
16
18
|
var cachedCurrentSlug = null;
|
|
17
19
|
|
|
20
|
+
// --- Countdown timer for upcoming schedules ---
|
|
21
|
+
var countdownTimer = null;
|
|
22
|
+
var countdownContainer = null;
|
|
23
|
+
|
|
18
24
|
// --- Session context menu ---
|
|
19
25
|
var sessionCtxMenu = null;
|
|
20
26
|
var sessionCtxSessionId = null;
|
|
@@ -115,6 +121,94 @@ function startInlineRename(sessionId, currentTitle) {
|
|
|
115
121
|
input.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
|
|
125
|
+
closeSessionCtxMenu();
|
|
126
|
+
|
|
127
|
+
var menu = document.createElement("div");
|
|
128
|
+
menu.className = "session-ctx-menu";
|
|
129
|
+
|
|
130
|
+
var renameItem = document.createElement("button");
|
|
131
|
+
renameItem.className = "session-ctx-item";
|
|
132
|
+
renameItem.innerHTML = iconHtml("pencil") + " <span>Rename</span>";
|
|
133
|
+
renameItem.addEventListener("click", function (e) {
|
|
134
|
+
e.stopPropagation();
|
|
135
|
+
closeSessionCtxMenu();
|
|
136
|
+
startLoopInlineRename(loopId, loopName);
|
|
137
|
+
});
|
|
138
|
+
menu.appendChild(renameItem);
|
|
139
|
+
|
|
140
|
+
var deleteItem = document.createElement("button");
|
|
141
|
+
deleteItem.className = "session-ctx-item session-ctx-delete";
|
|
142
|
+
deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
|
|
143
|
+
deleteItem.addEventListener("click", function (e) {
|
|
144
|
+
e.stopPropagation();
|
|
145
|
+
closeSessionCtxMenu();
|
|
146
|
+
var msg = 'Delete "' + (loopName || "Ralph Loop") + '"';
|
|
147
|
+
if (childCount > 1) msg += " and its " + childCount + " sessions";
|
|
148
|
+
msg += "? This cannot be undone.";
|
|
149
|
+
ctx.showConfirm(msg, function () {
|
|
150
|
+
if (ctx.ws && ctx.connected) {
|
|
151
|
+
ctx.ws.send(JSON.stringify({ type: "delete_loop_group", loopId: loopId }));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
menu.appendChild(deleteItem);
|
|
156
|
+
|
|
157
|
+
document.body.appendChild(menu);
|
|
158
|
+
sessionCtxMenu = menu;
|
|
159
|
+
refreshIcons();
|
|
160
|
+
|
|
161
|
+
requestAnimationFrame(function () {
|
|
162
|
+
var btnRect = anchorBtn.getBoundingClientRect();
|
|
163
|
+
menu.style.position = "fixed";
|
|
164
|
+
menu.style.top = (btnRect.bottom + 2) + "px";
|
|
165
|
+
menu.style.right = (window.innerWidth - btnRect.right) + "px";
|
|
166
|
+
menu.style.left = "auto";
|
|
167
|
+
var menuRect = menu.getBoundingClientRect();
|
|
168
|
+
if (menuRect.bottom > window.innerHeight - 8) {
|
|
169
|
+
menu.style.top = (btnRect.top - menuRect.height - 2) + "px";
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function startLoopInlineRename(loopId, currentName) {
|
|
175
|
+
var el = ctx.sessionListEl.querySelector('.session-loop-group[data-loop-id="' + loopId + '"]');
|
|
176
|
+
if (!el) return;
|
|
177
|
+
var textSpan = el.querySelector(".session-item-text");
|
|
178
|
+
if (!textSpan) return;
|
|
179
|
+
|
|
180
|
+
var input = document.createElement("input");
|
|
181
|
+
input.type = "text";
|
|
182
|
+
input.className = "session-rename-input";
|
|
183
|
+
input.value = currentName || "Ralph Loop";
|
|
184
|
+
|
|
185
|
+
var originalHtml = textSpan.innerHTML;
|
|
186
|
+
textSpan.innerHTML = "";
|
|
187
|
+
textSpan.appendChild(input);
|
|
188
|
+
input.focus();
|
|
189
|
+
input.select();
|
|
190
|
+
|
|
191
|
+
function commitRename() {
|
|
192
|
+
var newName = input.value.trim();
|
|
193
|
+
if (newName && newName !== currentName && ctx.ws && ctx.connected) {
|
|
194
|
+
ctx.ws.send(JSON.stringify({ type: "loop_registry_rename", id: loopId, name: newName }));
|
|
195
|
+
}
|
|
196
|
+
textSpan.innerHTML = originalHtml;
|
|
197
|
+
if (newName && newName !== currentName) {
|
|
198
|
+
// Update text inline immediately
|
|
199
|
+
var nameNode = textSpan.querySelector(".session-loop-name");
|
|
200
|
+
if (nameNode) nameNode.textContent = newName;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
input.addEventListener("keydown", function (e) {
|
|
205
|
+
if (e.key === "Enter") { e.preventDefault(); commitRename(); }
|
|
206
|
+
if (e.key === "Escape") { e.preventDefault(); textSpan.innerHTML = originalHtml; }
|
|
207
|
+
});
|
|
208
|
+
input.addEventListener("blur", commitRename);
|
|
209
|
+
input.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
210
|
+
}
|
|
211
|
+
|
|
118
212
|
function getDateGroup(ts) {
|
|
119
213
|
var now = new Date();
|
|
120
214
|
var d = new Date(ts);
|
|
@@ -139,6 +233,155 @@ function highlightMatch(text, query) {
|
|
|
139
233
|
return escapeHtml(before) + '<mark class="session-highlight">' + escapeHtml(match) + '</mark>' + escapeHtml(after);
|
|
140
234
|
}
|
|
141
235
|
|
|
236
|
+
function renderLoopChild(s) {
|
|
237
|
+
var el = document.createElement("div");
|
|
238
|
+
var isMatch = searchMatchIds !== null && searchMatchIds.has(s.id);
|
|
239
|
+
var dimmed = searchMatchIds !== null && !isMatch;
|
|
240
|
+
el.className = "session-loop-child" + (s.active ? " active" : "") + (isMatch ? " search-match" : "") + (dimmed ? " search-dimmed" : "");
|
|
241
|
+
el.dataset.sessionId = s.id;
|
|
242
|
+
|
|
243
|
+
var textSpan = document.createElement("span");
|
|
244
|
+
textSpan.className = "session-item-text";
|
|
245
|
+
var textHtml = "";
|
|
246
|
+
if (s.isProcessing) {
|
|
247
|
+
textHtml += '<span class="session-processing"></span>';
|
|
248
|
+
}
|
|
249
|
+
if (s.loop) {
|
|
250
|
+
var isRalphChild = s.loop.source === "ralph";
|
|
251
|
+
var roleName = s.loop.role === "crafting" ? "Crafting" : s.loop.role === "judge" ? "Judge" : (isRalphChild ? "Coder" : "Run");
|
|
252
|
+
var iterSuffix = s.loop.role === "crafting" ? "" : " #" + s.loop.iteration;
|
|
253
|
+
var roleCls = s.loop.role === "crafting" ? " crafting" : (!isRalphChild ? " scheduled" : "");
|
|
254
|
+
textHtml += '<span class="session-loop-role-badge' + roleCls + '">' + roleName + iterSuffix + '</span>';
|
|
255
|
+
}
|
|
256
|
+
textSpan.innerHTML = textHtml;
|
|
257
|
+
el.appendChild(textSpan);
|
|
258
|
+
|
|
259
|
+
el.addEventListener("click", (function (id) {
|
|
260
|
+
return function () {
|
|
261
|
+
if (ctx.ws && ctx.connected) {
|
|
262
|
+
ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
|
|
263
|
+
closeSidebar();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
})(s.id));
|
|
267
|
+
|
|
268
|
+
return el;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function renderLoopGroup(loopId, children, groupKey) {
|
|
272
|
+
var gk = groupKey || loopId;
|
|
273
|
+
// Sort children by iteration then role (coder before judge)
|
|
274
|
+
children.sort(function (a, b) {
|
|
275
|
+
var ai = (a.loop && a.loop.iteration) || 0;
|
|
276
|
+
var bi = (b.loop && b.loop.iteration) || 0;
|
|
277
|
+
if (ai !== bi) return ai - bi;
|
|
278
|
+
// coder before judge within same iteration
|
|
279
|
+
var ar = (a.loop && a.loop.role === "judge") ? 1 : 0;
|
|
280
|
+
var br = (b.loop && b.loop.role === "judge") ? 1 : 0;
|
|
281
|
+
return ar - br;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
var expanded = expandedLoopGroups.has(gk);
|
|
285
|
+
var hasActive = false;
|
|
286
|
+
var anyProcessing = false;
|
|
287
|
+
var latestSession = children[0];
|
|
288
|
+
for (var i = 0; i < children.length; i++) {
|
|
289
|
+
if (children[i].active) hasActive = true;
|
|
290
|
+
if (children[i].isProcessing) anyProcessing = true;
|
|
291
|
+
if ((children[i].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
292
|
+
latestSession = children[i];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
var loopName = (children[0].loop && children[0].loop.name) || "Ralph Loop";
|
|
297
|
+
var isRalph = children[0].loop && children[0].loop.source === "ralph";
|
|
298
|
+
var isCrafting = false;
|
|
299
|
+
var maxIter = 0;
|
|
300
|
+
for (var j = 0; j < children.length; j++) {
|
|
301
|
+
var iter = (children[j].loop && children[j].loop.iteration) || 0;
|
|
302
|
+
if (iter > maxIter) maxIter = iter;
|
|
303
|
+
if (children[j].loop && children[j].loop.role === "crafting") isCrafting = true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
var wrapper = document.createElement("div");
|
|
307
|
+
wrapper.className = "session-loop-wrapper";
|
|
308
|
+
|
|
309
|
+
// Group header row
|
|
310
|
+
var el = document.createElement("div");
|
|
311
|
+
el.className = "session-loop-group" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
|
|
312
|
+
el.dataset.loopId = loopId;
|
|
313
|
+
|
|
314
|
+
var chevron = document.createElement("button");
|
|
315
|
+
chevron.className = "session-loop-chevron";
|
|
316
|
+
chevron.innerHTML = iconHtml("chevron-right");
|
|
317
|
+
chevron.addEventListener("click", (function (lid) {
|
|
318
|
+
return function (e) {
|
|
319
|
+
e.stopPropagation();
|
|
320
|
+
if (expandedLoopGroups.has(lid)) {
|
|
321
|
+
expandedLoopGroups.delete(lid);
|
|
322
|
+
} else {
|
|
323
|
+
expandedLoopGroups.add(lid);
|
|
324
|
+
}
|
|
325
|
+
renderSessionList(null);
|
|
326
|
+
};
|
|
327
|
+
})(gk));
|
|
328
|
+
el.appendChild(chevron);
|
|
329
|
+
|
|
330
|
+
var textSpan = document.createElement("span");
|
|
331
|
+
textSpan.className = "session-item-text";
|
|
332
|
+
var textHtml = "";
|
|
333
|
+
if (anyProcessing) {
|
|
334
|
+
textHtml += '<span class="session-processing"></span>';
|
|
335
|
+
}
|
|
336
|
+
var groupIcon = isRalph ? "repeat" : "calendar-clock";
|
|
337
|
+
textHtml += '<span class="session-loop-icon' + (isRalph ? "" : " scheduled") + '">' + iconHtml(groupIcon) + '</span>';
|
|
338
|
+
textHtml += '<span class="session-loop-name">' + escapeHtml(loopName) + '</span>';
|
|
339
|
+
if (isCrafting && children.length === 1) {
|
|
340
|
+
textHtml += '<span class="session-loop-badge crafting">Crafting</span>';
|
|
341
|
+
} else {
|
|
342
|
+
textHtml += '<span class="session-loop-count' + (isRalph ? "" : " scheduled") + '">' + children.length + '</span>';
|
|
343
|
+
}
|
|
344
|
+
textSpan.innerHTML = textHtml;
|
|
345
|
+
el.appendChild(textSpan);
|
|
346
|
+
|
|
347
|
+
// More button (ellipsis)
|
|
348
|
+
var moreBtn = document.createElement("button");
|
|
349
|
+
moreBtn.className = "session-more-btn";
|
|
350
|
+
moreBtn.innerHTML = iconHtml("ellipsis");
|
|
351
|
+
moreBtn.title = "More options";
|
|
352
|
+
moreBtn.addEventListener("click", (function (lid, name, count, btn) {
|
|
353
|
+
return function (e) {
|
|
354
|
+
e.stopPropagation();
|
|
355
|
+
showLoopCtxMenu(btn, lid, name, count);
|
|
356
|
+
};
|
|
357
|
+
})(loopId, loopName, children.length, moreBtn));
|
|
358
|
+
el.appendChild(moreBtn);
|
|
359
|
+
|
|
360
|
+
// Click row (not chevron/more) → switch to latest session
|
|
361
|
+
el.addEventListener("click", (function (id) {
|
|
362
|
+
return function () {
|
|
363
|
+
if (ctx.ws && ctx.connected) {
|
|
364
|
+
ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
|
|
365
|
+
closeSidebar();
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
})(latestSession.id));
|
|
369
|
+
|
|
370
|
+
wrapper.appendChild(el);
|
|
371
|
+
|
|
372
|
+
// Expanded children
|
|
373
|
+
if (expanded) {
|
|
374
|
+
var childContainer = document.createElement("div");
|
|
375
|
+
childContainer.className = "session-loop-children";
|
|
376
|
+
for (var k = 0; k < children.length; k++) {
|
|
377
|
+
childContainer.appendChild(renderLoopChild(children[k]));
|
|
378
|
+
}
|
|
379
|
+
wrapper.appendChild(childContainer);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return wrapper;
|
|
383
|
+
}
|
|
384
|
+
|
|
142
385
|
function renderSessionItem(s) {
|
|
143
386
|
var el = document.createElement("div");
|
|
144
387
|
var isMatch = searchMatchIds !== null && searchMatchIds.has(s.id);
|
|
@@ -185,15 +428,51 @@ export function renderSessionList(sessions) {
|
|
|
185
428
|
|
|
186
429
|
ctx.sessionListEl.innerHTML = "";
|
|
187
430
|
|
|
188
|
-
//
|
|
189
|
-
|
|
431
|
+
// Partition: loop sessions vs normal sessions
|
|
432
|
+
// Group by loopId + startedAt so different runs of the same task are separate groups
|
|
433
|
+
var loopGroups = {}; // groupKey -> [sessions]
|
|
434
|
+
var normalSessions = [];
|
|
435
|
+
for (var i = 0; i < cachedSessions.length; i++) {
|
|
436
|
+
var s = cachedSessions[i];
|
|
437
|
+
if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph") {
|
|
438
|
+
// Task crafting sessions live in the scheduler calendar, not the main list
|
|
439
|
+
continue;
|
|
440
|
+
} else if (s.loop && s.loop.loopId) {
|
|
441
|
+
var groupKey = s.loop.loopId + ":" + (s.loop.startedAt || 0);
|
|
442
|
+
if (!loopGroups[groupKey]) loopGroups[groupKey] = [];
|
|
443
|
+
loopGroups[groupKey].push(s);
|
|
444
|
+
} else {
|
|
445
|
+
normalSessions.push(s);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Build virtual items: normal sessions + one entry per loop group (using latest child's lastActivity)
|
|
450
|
+
var items = [];
|
|
451
|
+
for (var j = 0; j < normalSessions.length; j++) {
|
|
452
|
+
items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
|
|
453
|
+
}
|
|
454
|
+
var groupKeys = Object.keys(loopGroups);
|
|
455
|
+
for (var k = 0; k < groupKeys.length; k++) {
|
|
456
|
+
var gk = groupKeys[k];
|
|
457
|
+
var children = loopGroups[gk];
|
|
458
|
+
var realLoopId = children[0].loop.loopId;
|
|
459
|
+
var maxActivity = 0;
|
|
460
|
+
for (var m = 0; m < children.length; m++) {
|
|
461
|
+
var act = children[m].lastActivity || 0;
|
|
462
|
+
if (act > maxActivity) maxActivity = act;
|
|
463
|
+
}
|
|
464
|
+
items.push({ type: "loop", loopId: realLoopId, groupKey: gk, children: children, lastActivity: maxActivity });
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Sort by lastActivity descending
|
|
468
|
+
items.sort(function (a, b) {
|
|
190
469
|
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
191
470
|
});
|
|
192
471
|
|
|
193
472
|
var currentGroup = "";
|
|
194
|
-
for (var
|
|
195
|
-
var
|
|
196
|
-
var group = getDateGroup(
|
|
473
|
+
for (var n = 0; n < items.length; n++) {
|
|
474
|
+
var item = items[n];
|
|
475
|
+
var group = getDateGroup(item.lastActivity || 0);
|
|
197
476
|
if (group !== currentGroup) {
|
|
198
477
|
currentGroup = group;
|
|
199
478
|
var header = document.createElement("div");
|
|
@@ -201,7 +480,11 @@ export function renderSessionList(sessions) {
|
|
|
201
480
|
header.textContent = group;
|
|
202
481
|
ctx.sessionListEl.appendChild(header);
|
|
203
482
|
}
|
|
204
|
-
|
|
483
|
+
if (item.type === "loop") {
|
|
484
|
+
ctx.sessionListEl.appendChild(renderLoopGroup(item.loopId, item.children, item.groupKey));
|
|
485
|
+
} else {
|
|
486
|
+
ctx.sessionListEl.appendChild(renderSessionItem(item.data));
|
|
487
|
+
}
|
|
205
488
|
}
|
|
206
489
|
refreshIcons();
|
|
207
490
|
updatePageTitle();
|
|
@@ -356,6 +639,18 @@ function renderSheetProjects(listEl) {
|
|
|
356
639
|
}
|
|
357
640
|
|
|
358
641
|
function renderSheetSessions(listEl) {
|
|
642
|
+
// New session button at top
|
|
643
|
+
var newBtn = document.createElement("button");
|
|
644
|
+
newBtn.className = "mobile-session-new";
|
|
645
|
+
newBtn.innerHTML = '<i data-lucide="plus" style="width:16px;height:16px"></i> New session';
|
|
646
|
+
newBtn.addEventListener("click", function () {
|
|
647
|
+
if (ctx.ws && ctx.connected) {
|
|
648
|
+
ctx.ws.send(JSON.stringify({ type: "new_session" }));
|
|
649
|
+
}
|
|
650
|
+
closeMobileSheet();
|
|
651
|
+
});
|
|
652
|
+
listEl.appendChild(newBtn);
|
|
653
|
+
|
|
359
654
|
var sorted = cachedSessions.slice().sort(function (a, b) {
|
|
360
655
|
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
361
656
|
});
|
|
@@ -435,6 +730,14 @@ export function initSidebar(_ctx) {
|
|
|
435
730
|
}
|
|
436
731
|
});
|
|
437
732
|
|
|
733
|
+
// --- New Ralph Loop button ---
|
|
734
|
+
var newRalphBtn = ctx.$("new-ralph-btn");
|
|
735
|
+
if (newRalphBtn) {
|
|
736
|
+
newRalphBtn.addEventListener("click", function () {
|
|
737
|
+
if (ctx.openRalphWizard) ctx.openRalphWizard();
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
438
741
|
// --- Session search ---
|
|
439
742
|
var searchBtn = ctx.$("search-session-btn");
|
|
440
743
|
var searchBox = ctx.$("session-search");
|
|
@@ -577,7 +880,7 @@ export function initSidebar(_ctx) {
|
|
|
577
880
|
// --- Mobile tab bar ---
|
|
578
881
|
var mobileTabBar = document.getElementById("mobile-tab-bar");
|
|
579
882
|
var mobileTabs = mobileTabBar ? mobileTabBar.querySelectorAll(".mobile-tab") : [];
|
|
580
|
-
var
|
|
883
|
+
var mobileHomeBtn = document.getElementById("mobile-home-btn");
|
|
581
884
|
|
|
582
885
|
function setMobileTabActive(tabName) {
|
|
583
886
|
for (var i = 0; i < mobileTabs.length; i++) {
|
|
@@ -615,13 +918,11 @@ export function initSidebar(_ctx) {
|
|
|
615
918
|
})(mobileTabs[t]);
|
|
616
919
|
}
|
|
617
920
|
|
|
618
|
-
if (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
setMobileTabActive("");
|
|
624
|
-
}
|
|
921
|
+
if (mobileHomeBtn) {
|
|
922
|
+
mobileHomeBtn.addEventListener("click", function () {
|
|
923
|
+
closeSidebar();
|
|
924
|
+
setMobileTabActive("");
|
|
925
|
+
if (ctx.showHomeHub) ctx.showHomeHub();
|
|
625
926
|
});
|
|
626
927
|
}
|
|
627
928
|
|
|
@@ -710,6 +1011,54 @@ export function initSidebar(_ctx) {
|
|
|
710
1011
|
|
|
711
1012
|
// Initial sync even if no resize handle
|
|
712
1013
|
syncUserIslandWidth();
|
|
1014
|
+
|
|
1015
|
+
// --- Schedule countdown timer ---
|
|
1016
|
+
startCountdownTimer();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function startCountdownTimer() {
|
|
1020
|
+
if (countdownTimer) clearInterval(countdownTimer);
|
|
1021
|
+
countdownTimer = setInterval(updateCountdowns, 1000);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function updateCountdowns() {
|
|
1025
|
+
if (!ctx || !ctx.getUpcomingSchedules || !ctx.sessionListEl) return;
|
|
1026
|
+
var upcoming = ctx.getUpcomingSchedules(3 * 60 * 1000); // 3 minutes
|
|
1027
|
+
|
|
1028
|
+
// Remove stale container
|
|
1029
|
+
if (countdownContainer && !ctx.sessionListEl.contains(countdownContainer)) {
|
|
1030
|
+
countdownContainer = null;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (upcoming.length === 0) {
|
|
1034
|
+
if (countdownContainer) {
|
|
1035
|
+
countdownContainer.remove();
|
|
1036
|
+
countdownContainer = null;
|
|
1037
|
+
}
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
if (!countdownContainer) {
|
|
1042
|
+
countdownContainer = document.createElement("div");
|
|
1043
|
+
countdownContainer.className = "session-countdown-group";
|
|
1044
|
+
ctx.sessionListEl.insertBefore(countdownContainer, ctx.sessionListEl.firstChild);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
var html = "";
|
|
1048
|
+
var now = Date.now();
|
|
1049
|
+
for (var i = 0; i < upcoming.length; i++) {
|
|
1050
|
+
var u = upcoming[i];
|
|
1051
|
+
var remaining = Math.max(0, Math.ceil((u.nextRunAt - now) / 1000));
|
|
1052
|
+
var min = Math.floor(remaining / 60);
|
|
1053
|
+
var sec = remaining % 60;
|
|
1054
|
+
var timeStr = min + ":" + (sec < 10 ? "0" : "") + sec;
|
|
1055
|
+
var colorStyle = u.color ? " style=\"border-left-color:" + u.color + "\"" : "";
|
|
1056
|
+
html += '<div class="session-countdown-item"' + colorStyle + '>';
|
|
1057
|
+
html += '<span class="session-countdown-name">' + escapeHtml(u.name) + '</span>';
|
|
1058
|
+
html += '<span class="session-countdown-badge">' + timeStr + '</span>';
|
|
1059
|
+
html += '</div>';
|
|
1060
|
+
}
|
|
1061
|
+
countdownContainer.innerHTML = html;
|
|
713
1062
|
}
|
|
714
1063
|
|
|
715
1064
|
// --- CLI session picker ---
|
|
@@ -1249,11 +1598,10 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
1249
1598
|
deleteItem.addEventListener("click", function (e) {
|
|
1250
1599
|
e.stopPropagation();
|
|
1251
1600
|
closeProjectCtxMenu();
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
});
|
|
1601
|
+
// Check for tasks/schedules first before removing
|
|
1602
|
+
if (ctx.ws && ctx.connected) {
|
|
1603
|
+
ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name }));
|
|
1604
|
+
}
|
|
1257
1605
|
});
|
|
1258
1606
|
menu.appendChild(deleteItem);
|
|
1259
1607
|
|
|
@@ -1366,9 +1714,7 @@ function showEmojiPicker(slug, anchorEl) {
|
|
|
1366
1714
|
grid.appendChild(btn);
|
|
1367
1715
|
})(emojis[i]);
|
|
1368
1716
|
}
|
|
1369
|
-
|
|
1370
|
-
twemoji.parse(grid, { folder: "svg", ext: ".svg" });
|
|
1371
|
-
}
|
|
1717
|
+
parseEmojis(grid);
|
|
1372
1718
|
scrollArea.scrollTop = 0;
|
|
1373
1719
|
}
|
|
1374
1720
|
|
|
@@ -1383,9 +1729,7 @@ function showEmojiPicker(slug, anchorEl) {
|
|
|
1383
1729
|
buildGrid(EMOJI_CATEGORIES[0].emojis);
|
|
1384
1730
|
|
|
1385
1731
|
// Parse tabs with twemoji
|
|
1386
|
-
|
|
1387
|
-
twemoji.parse(tabBar, { folder: "svg", ext: ".svg" });
|
|
1388
|
-
}
|
|
1732
|
+
parseEmojis(tabBar);
|
|
1389
1733
|
|
|
1390
1734
|
document.body.appendChild(picker);
|
|
1391
1735
|
emojiPickerEl = picker;
|
|
@@ -1489,8 +1833,8 @@ function showTrashZone() {
|
|
|
1489
1833
|
// Spawn dust particles at trash position
|
|
1490
1834
|
var rect = trash.getBoundingClientRect();
|
|
1491
1835
|
spawnDustParticles(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
1492
|
-
//
|
|
1493
|
-
ctx.ws.send(JSON.stringify({ type: "
|
|
1836
|
+
// Check for tasks before removing
|
|
1837
|
+
ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug }));
|
|
1494
1838
|
}
|
|
1495
1839
|
});
|
|
1496
1840
|
}
|
|
@@ -1649,9 +1993,7 @@ export function renderIconStrip(projects, currentSlug) {
|
|
|
1649
1993
|
var emojiSpan = document.createElement("span");
|
|
1650
1994
|
emojiSpan.className = "project-emoji";
|
|
1651
1995
|
emojiSpan.textContent = p.icon;
|
|
1652
|
-
|
|
1653
|
-
twemoji.parse(emojiSpan, { folder: "svg", ext: ".svg" });
|
|
1654
|
-
}
|
|
1996
|
+
parseEmojis(emojiSpan);
|
|
1655
1997
|
el.appendChild(emojiSpan);
|
|
1656
1998
|
} else {
|
|
1657
1999
|
el.appendChild(document.createTextNode(getProjectAbbrev(p.name)));
|
|
@@ -1771,11 +2113,16 @@ export function initIconStrip(_ctx) {
|
|
|
1771
2113
|
exploreBtn.addEventListener("mouseleave", hideIconTooltip);
|
|
1772
2114
|
}
|
|
1773
2115
|
|
|
1774
|
-
// Tooltip for home icon
|
|
2116
|
+
// Tooltip + click for home icon
|
|
1775
2117
|
var homeIcon = document.querySelector(".icon-strip-home");
|
|
1776
2118
|
if (homeIcon) {
|
|
1777
2119
|
homeIcon.addEventListener("mouseenter", function () { showIconTooltip(homeIcon, "Clay"); });
|
|
1778
2120
|
homeIcon.addEventListener("mouseleave", hideIconTooltip);
|
|
2121
|
+
homeIcon.addEventListener("click", function (e) {
|
|
2122
|
+
e.preventDefault();
|
|
2123
|
+
if (_ctx.showHomeHub) _ctx.showHomeHub();
|
|
2124
|
+
});
|
|
2125
|
+
homeIcon.style.cursor = "pointer";
|
|
1779
2126
|
}
|
|
1780
2127
|
|
|
1781
2128
|
// Chevron dropdown on project name
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
2
|
+
import { parseEmojis } from './markdown.js';
|
|
2
3
|
|
|
3
4
|
var ctx;
|
|
4
5
|
var notes = new Map(); // id -> { data, el }
|
|
@@ -1123,6 +1124,7 @@ function renderArchiveCards() {
|
|
|
1123
1124
|
var bodyLines = (noteData.data.text || "").split("\n").slice(1).join("\n").trim();
|
|
1124
1125
|
if (bodyLines) {
|
|
1125
1126
|
body.innerHTML = renderMiniMarkdown("_\n" + bodyLines).replace('<div class="sn-title">_</div>', "");
|
|
1127
|
+
parseEmojis(body);
|
|
1126
1128
|
}
|
|
1127
1129
|
card.appendChild(body);
|
|
1128
1130
|
|