clay-server 2.47.0-beta.5 → 3.0.0-beta.1
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-http.js +5 -2
- 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 +24 -1
- package/lib/public/css/icon-strip.css +0 -190
- package/lib/public/css/menus.css +83 -1
- package/lib/public/css/messages.css +14 -0
- package/lib/public/css/mobile-nav.css +4 -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/css/title-bar.css +169 -0
- package/lib/public/index.html +10 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-favicon.js +6 -10
- package/lib/public/modules/app-header.js +101 -6
- package/lib/public/modules/app-home-hub.js +2 -0
- package/lib/public/modules/app-messages.js +63 -13
- package/lib/public/modules/app-panels.js +49 -27
- package/lib/public/modules/app-projects.js +43 -30
- package/lib/public/modules/app-rendering.js +11 -4
- package/lib/public/modules/branch-switcher.js +178 -0
- package/lib/public/modules/dom-refs.js +0 -1
- package/lib/public/modules/pane-bridge.js +41 -0
- package/lib/public/modules/pane-session.js +18 -0
- package/lib/public/modules/project-switcher.js +2 -1
- package/lib/public/modules/sidebar-mobile.js +19 -6
- package/lib/public/modules/sidebar-projects.js +63 -236
- 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/modules/worktree-family.js +60 -0
- 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 +1 -1
|
@@ -10,9 +10,10 @@ import { store } from './store.js';
|
|
|
10
10
|
import { getWs } from './ws-ref.js';
|
|
11
11
|
import { closeSidebar } from './sidebar.js';
|
|
12
12
|
import { showIconTooltip, hideIconTooltip, closeUserCtxMenu, getCurrentDmUserId } from './sidebar-mates.js';
|
|
13
|
-
import { switchProject, openAddProjectModal
|
|
13
|
+
import { switchProject, openAddProjectModal } from './app-projects.js';
|
|
14
14
|
import { showHomeHub } from './app-home-hub.js';
|
|
15
15
|
import { getPendingTuiAttention } from './app-notifications.js';
|
|
16
|
+
import { aggregateFamily } from './worktree-family.js';
|
|
16
17
|
|
|
17
18
|
// --- Project state ---
|
|
18
19
|
var cachedProjectList = [];
|
|
@@ -31,12 +32,6 @@ var emojiPickerEl = null;
|
|
|
31
32
|
var draggedSlug = null;
|
|
32
33
|
var draggedEl = null;
|
|
33
34
|
|
|
34
|
-
// --- Worktree folder collapse state (persisted in localStorage) ---
|
|
35
|
-
var wtCollapsed = {};
|
|
36
|
-
try {
|
|
37
|
-
wtCollapsed = JSON.parse(localStorage.getItem("clay-wt-collapsed") || "{}");
|
|
38
|
-
} catch (e) {}
|
|
39
|
-
|
|
40
35
|
var EMOJI_CATEGORIES = [
|
|
41
36
|
{ id: "frequent", icon: "🕐", label: "Frequent", emojis: [
|
|
42
37
|
"😀","😎","🤓","🧠","💡","🔥","⚡","🚀",
|
|
@@ -524,17 +519,8 @@ function showIconCtxMenu(anchorEl, slug, name) {
|
|
|
524
519
|
}
|
|
525
520
|
});
|
|
526
521
|
menu.appendChild(removeWtItem);
|
|
527
|
-
} else {
|
|
528
|
-
var wtItem = document.createElement("button");
|
|
529
|
-
wtItem.className = "project-ctx-item";
|
|
530
|
-
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
531
|
-
wtItem.addEventListener("click", function (e) {
|
|
532
|
-
e.stopPropagation();
|
|
533
|
-
closeProjectCtxMenu();
|
|
534
|
-
showWorktreeModal(slug, name || slug);
|
|
535
|
-
});
|
|
536
|
-
menu.appendChild(wtItem);
|
|
537
522
|
}
|
|
523
|
+
// Worktree creation lives in the title-bar branch chip, not here.
|
|
538
524
|
|
|
539
525
|
document.body.appendChild(menu);
|
|
540
526
|
projectCtxMenu = menu;
|
|
@@ -619,20 +605,7 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
619
605
|
}
|
|
620
606
|
}
|
|
621
607
|
|
|
622
|
-
|
|
623
|
-
sep2.className = "project-ctx-separator";
|
|
624
|
-
menu.appendChild(sep2);
|
|
625
|
-
|
|
626
|
-
// --- Add Worktree ---
|
|
627
|
-
var wtItem = document.createElement("button");
|
|
628
|
-
wtItem.className = "project-ctx-item";
|
|
629
|
-
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
630
|
-
wtItem.addEventListener("click", function (e) {
|
|
631
|
-
e.stopPropagation();
|
|
632
|
-
closeProjectCtxMenu();
|
|
633
|
-
showWorktreeModal(slug, name || slug);
|
|
634
|
-
});
|
|
635
|
-
menu.appendChild(wtItem);
|
|
608
|
+
// Worktree creation lives in the title-bar branch chip, not here.
|
|
636
609
|
|
|
637
610
|
if (!store.get('permissions') || store.get('permissions').deleteProject !== false) {
|
|
638
611
|
var sep3 = document.createElement("div");
|
|
@@ -953,13 +926,6 @@ function setupDragHandlers(el, slug) {
|
|
|
953
926
|
});
|
|
954
927
|
}
|
|
955
928
|
|
|
956
|
-
// --- Worktree folder collapse ---
|
|
957
|
-
|
|
958
|
-
function setWtCollapsed(slug, collapsed) {
|
|
959
|
-
wtCollapsed[slug] = collapsed;
|
|
960
|
-
try { localStorage.setItem("clay-wt-collapsed", JSON.stringify(wtCollapsed)); } catch (e) {}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
929
|
function groupProjects(projects) {
|
|
964
930
|
var parents = [];
|
|
965
931
|
var wtByParent = {};
|
|
@@ -975,6 +941,21 @@ function groupProjects(projects) {
|
|
|
975
941
|
return { parents: parents, wtByParent: wtByParent };
|
|
976
942
|
}
|
|
977
943
|
|
|
944
|
+
function aggregateProjectFamily(parent, worktrees) {
|
|
945
|
+
var aggregate = aggregateFamily(parent, worktrees);
|
|
946
|
+
aggregate.hasFamilyPendingAttention = getPendingTuiAttention(parent.slug) > 0;
|
|
947
|
+
aggregate.hasWorktreeUnread = false;
|
|
948
|
+
aggregate.hasWorktreePending = false;
|
|
949
|
+
for (var i = 0; i < worktrees.length; i++) {
|
|
950
|
+
if (getPendingTuiAttention(worktrees[i].slug) > 0) aggregate.hasFamilyPendingAttention = true;
|
|
951
|
+
if (worktrees[i].unread > 0) aggregate.hasWorktreeUnread = true;
|
|
952
|
+
if (worktrees[i].pendingPermissions > 0 || getPendingTuiAttention(worktrees[i].slug) > 0) {
|
|
953
|
+
aggregate.hasWorktreePending = true;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return aggregate;
|
|
957
|
+
}
|
|
958
|
+
|
|
978
959
|
// --- Icon item creation ---
|
|
979
960
|
|
|
980
961
|
function createIconItem(p, currentSlug) {
|
|
@@ -1006,7 +987,7 @@ function createIconItem(p, currentSlug) {
|
|
|
1006
987
|
|
|
1007
988
|
var projectBadge = document.createElement("span");
|
|
1008
989
|
projectBadge.className = "icon-strip-project-badge";
|
|
1009
|
-
if (p.unread > 0 && !isActive) {
|
|
990
|
+
if (p.unread > 0 && (!isActive || p.hasWorktreeUnread)) {
|
|
1010
991
|
projectBadge.textContent = p.unread > 99 ? "99+" : String(p.unread);
|
|
1011
992
|
projectBadge.classList.add("has-unread");
|
|
1012
993
|
}
|
|
@@ -1016,7 +997,8 @@ function createIconItem(p, currentSlug) {
|
|
|
1016
997
|
// broadcast — they surface via tui_attention notifications. We shake the
|
|
1017
998
|
// same icon for either, so the user notices regardless of which engine
|
|
1018
999
|
// is asking.
|
|
1019
|
-
if (!isActive && (p.pendingPermissions > 0 || getPendingTuiAttention(p.slug) > 0))
|
|
1000
|
+
if ((!isActive && (p.pendingPermissions > 0 || p.hasFamilyPendingAttention || getPendingTuiAttention(p.slug) > 0)) ||
|
|
1001
|
+
p.hasWorktreePending) {
|
|
1020
1002
|
el.classList.add("has-pending-perm");
|
|
1021
1003
|
}
|
|
1022
1004
|
|
|
@@ -1037,7 +1019,7 @@ function createIconItem(p, currentSlug) {
|
|
|
1037
1019
|
|
|
1038
1020
|
// --- Worktree creation modal ---
|
|
1039
1021
|
|
|
1040
|
-
function showWorktreeModal(parentSlug, parentName) {
|
|
1022
|
+
export function showWorktreeModal(parentSlug, parentName) {
|
|
1041
1023
|
var existing = document.getElementById("wt-modal-container");
|
|
1042
1024
|
if (existing) existing.remove();
|
|
1043
1025
|
|
|
@@ -1192,164 +1174,17 @@ export function renderIconStrip(projects, currentSlug) {
|
|
|
1192
1174
|
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1193
1175
|
var p = grouped.parents[i];
|
|
1194
1176
|
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1195
|
-
var
|
|
1196
|
-
|
|
1197
|
-
if (!hasWorktrees) {
|
|
1198
|
-
var el = createIconItem(p, currentSlug);
|
|
1199
|
-
(function (slug, name, elem) {
|
|
1200
|
-
elem.addEventListener("contextmenu", function (e) {
|
|
1201
|
-
e.preventDefault();
|
|
1202
|
-
e.stopPropagation();
|
|
1203
|
-
showIconCtxMenu(elem, slug, name);
|
|
1204
|
-
});
|
|
1205
|
-
})(p.slug, p.name || p.slug, el);
|
|
1206
|
-
setupDragHandlers(el, p.slug);
|
|
1207
|
-
container.appendChild(el);
|
|
1208
|
-
continue;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// Folder group for parent + worktrees
|
|
1212
|
-
var folder = document.createElement("div");
|
|
1213
|
-
folder.className = "icon-strip-group";
|
|
1214
|
-
folder.dataset.parentSlug = p.slug;
|
|
1215
|
-
if (wtCollapsed[p.slug]) folder.classList.add("collapsed");
|
|
1216
|
-
|
|
1217
|
-
if (!p.isProcessing) {
|
|
1218
|
-
for (var wpi = 0; wpi < worktrees.length; wpi++) {
|
|
1219
|
-
if (worktrees[wpi].isProcessing) { p.isProcessing = true; break; }
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
var header = createIconItem(p, currentSlug);
|
|
1224
|
-
header.classList.add("folder-header");
|
|
1177
|
+
var familyProject = aggregateProjectFamily(p, worktrees);
|
|
1178
|
+
var el = createIconItem(familyProject, currentSlug);
|
|
1225
1179
|
(function (slug, name, elem) {
|
|
1226
1180
|
elem.addEventListener("contextmenu", function (e) {
|
|
1227
1181
|
e.preventDefault();
|
|
1228
1182
|
e.stopPropagation();
|
|
1229
1183
|
showIconCtxMenu(elem, slug, name);
|
|
1230
1184
|
});
|
|
1231
|
-
})(p.slug, p.name || p.slug,
|
|
1232
|
-
setupDragHandlers(
|
|
1233
|
-
|
|
1234
|
-
var chevron = document.createElement("span");
|
|
1235
|
-
chevron.className = "icon-strip-group-chevron";
|
|
1236
|
-
chevron.innerHTML = '<i data-lucide="git-branch"></i>';
|
|
1237
|
-
(function (parentSlug, folderEl) {
|
|
1238
|
-
chevron.addEventListener("click", function (e) {
|
|
1239
|
-
e.preventDefault();
|
|
1240
|
-
e.stopPropagation();
|
|
1241
|
-
var nowCollapsed = folderEl.classList.toggle("collapsed");
|
|
1242
|
-
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1243
|
-
});
|
|
1244
|
-
chevron.addEventListener("contextmenu", function (e) {
|
|
1245
|
-
e.preventDefault();
|
|
1246
|
-
e.stopPropagation();
|
|
1247
|
-
});
|
|
1248
|
-
})(p.slug, folder);
|
|
1249
|
-
chevron.setAttribute("data-tip", "Toggle worktrees");
|
|
1250
|
-
header.appendChild(chevron);
|
|
1251
|
-
folder.appendChild(header);
|
|
1252
|
-
|
|
1253
|
-
var itemsContainer = document.createElement("div");
|
|
1254
|
-
itemsContainer.className = "icon-strip-group-items";
|
|
1255
|
-
|
|
1256
|
-
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1257
|
-
(function (wt) {
|
|
1258
|
-
var wtEl = document.createElement("a");
|
|
1259
|
-
var isWtActive = wt.slug === currentSlug && !currentDmUserId;
|
|
1260
|
-
var isAccessible = wt.worktreeAccessible !== false;
|
|
1261
|
-
wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (!isAccessible ? " wt-disabled" : "");
|
|
1262
|
-
wtEl.href = "/p/" + wt.slug + "/";
|
|
1263
|
-
wtEl.dataset.slug = wt.slug;
|
|
1264
|
-
|
|
1265
|
-
if (wt.icon) {
|
|
1266
|
-
var wtEmoji = document.createElement("span");
|
|
1267
|
-
wtEmoji.className = "wt-branch-abbrev project-emoji";
|
|
1268
|
-
wtEmoji.textContent = wt.icon;
|
|
1269
|
-
parseEmojis(wtEmoji);
|
|
1270
|
-
wtEl.appendChild(wtEmoji);
|
|
1271
|
-
} else {
|
|
1272
|
-
var abbrev = document.createElement("span");
|
|
1273
|
-
abbrev.className = "wt-branch-abbrev";
|
|
1274
|
-
abbrev.textContent = getProjectAbbrev(wt.name);
|
|
1275
|
-
wtEl.appendChild(abbrev);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
var wtStatus = document.createElement("span");
|
|
1279
|
-
wtStatus.className = "icon-strip-status";
|
|
1280
|
-
if (wt.isProcessing) wtStatus.classList.add("processing");
|
|
1281
|
-
wtEl.appendChild(wtStatus);
|
|
1282
|
-
|
|
1283
|
-
var tooltipText = wt.name;
|
|
1284
|
-
if (!isAccessible) {
|
|
1285
|
-
tooltipText += " (outside project path, cannot be accessed)";
|
|
1286
|
-
}
|
|
1287
|
-
(function (text, elem) {
|
|
1288
|
-
elem.addEventListener("mouseenter", function () { showIconTooltip(elem, text); });
|
|
1289
|
-
elem.addEventListener("mouseleave", hideIconTooltip);
|
|
1290
|
-
})(tooltipText, wtEl);
|
|
1291
|
-
|
|
1292
|
-
if (isAccessible) {
|
|
1293
|
-
(function (slug) {
|
|
1294
|
-
wtEl.addEventListener("click", function (e) {
|
|
1295
|
-
e.preventDefault();
|
|
1296
|
-
if (switchProject) switchProject(slug);
|
|
1297
|
-
});
|
|
1298
|
-
})(wt.slug);
|
|
1299
|
-
} else {
|
|
1300
|
-
wtEl.addEventListener("click", function (e) {
|
|
1301
|
-
e.preventDefault();
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
if (isAccessible) {
|
|
1306
|
-
(function (slug, name, elem) {
|
|
1307
|
-
elem.addEventListener("contextmenu", function (e) {
|
|
1308
|
-
e.preventDefault();
|
|
1309
|
-
e.stopPropagation();
|
|
1310
|
-
showIconCtxMenu(elem, slug, name);
|
|
1311
|
-
});
|
|
1312
|
-
})(wt.slug, wt.name, wtEl);
|
|
1313
|
-
} else {
|
|
1314
|
-
wtEl.addEventListener("contextmenu", function (e) {
|
|
1315
|
-
e.preventDefault();
|
|
1316
|
-
e.stopPropagation();
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
if (!isWtActive && (wt.pendingPermissions > 0 || getPendingTuiAttention(wt.slug) > 0)) {
|
|
1321
|
-
wtEl.classList.add("has-pending-perm");
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
itemsContainer.appendChild(wtEl);
|
|
1325
|
-
})(worktrees[wi]);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
var hasWtPendingPerm = false;
|
|
1329
|
-
for (var wpi2 = 0; wpi2 < worktrees.length; wpi2++) {
|
|
1330
|
-
if (worktrees[wpi2].pendingPermissions > 0 || getPendingTuiAttention(worktrees[wpi2].slug) > 0) {
|
|
1331
|
-
hasWtPendingPerm = true;
|
|
1332
|
-
break;
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
if (hasWtPendingPerm) folder.classList.remove("collapsed");
|
|
1336
|
-
|
|
1337
|
-
var addBtn = document.createElement("button");
|
|
1338
|
-
addBtn.className = "icon-strip-group-add";
|
|
1339
|
-
addBtn.textContent = "+";
|
|
1340
|
-
(function (parentSlug, parentName, btn) {
|
|
1341
|
-
btn.addEventListener("click", function (e) {
|
|
1342
|
-
e.preventDefault();
|
|
1343
|
-
e.stopPropagation();
|
|
1344
|
-
showWorktreeModal(parentSlug, parentName);
|
|
1345
|
-
});
|
|
1346
|
-
btn.addEventListener("mouseenter", function () { showIconTooltip(btn, "New worktree"); });
|
|
1347
|
-
btn.addEventListener("mouseleave", hideIconTooltip);
|
|
1348
|
-
})(p.slug, p.name, addBtn);
|
|
1349
|
-
itemsContainer.appendChild(addBtn);
|
|
1350
|
-
|
|
1351
|
-
folder.appendChild(itemsContainer);
|
|
1352
|
-
container.appendChild(folder);
|
|
1185
|
+
})(p.slug, p.name || p.slug, el);
|
|
1186
|
+
setupDragHandlers(el, p.slug);
|
|
1187
|
+
container.appendChild(el);
|
|
1353
1188
|
}
|
|
1354
1189
|
|
|
1355
1190
|
// Update home icon active state
|
|
@@ -1377,50 +1212,17 @@ function renderProjectList(projects, currentSlug) {
|
|
|
1377
1212
|
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1378
1213
|
var p = grouped.parents[i];
|
|
1379
1214
|
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1380
|
-
|
|
1381
|
-
if (worktrees.length === 0) {
|
|
1382
|
-
list.appendChild(createMobileProjectItem(p, currentSlug, false));
|
|
1383
|
-
continue;
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
var folderDiv = document.createElement("div");
|
|
1387
|
-
folderDiv.className = "mobile-project-folder";
|
|
1388
|
-
if (wtCollapsed[p.slug]) folderDiv.classList.add("collapsed");
|
|
1389
|
-
|
|
1390
|
-
var headerEl = createMobileProjectItem(p, currentSlug, false);
|
|
1391
|
-
var chevron = document.createElement("span");
|
|
1392
|
-
chevron.className = "mobile-folder-chevron";
|
|
1393
|
-
chevron.innerHTML = "▼";
|
|
1394
|
-
(function (parentSlug, fDiv) {
|
|
1395
|
-
chevron.addEventListener("click", function (e) {
|
|
1396
|
-
e.preventDefault();
|
|
1397
|
-
e.stopPropagation();
|
|
1398
|
-
var nowCollapsed = fDiv.classList.toggle("collapsed");
|
|
1399
|
-
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1400
|
-
});
|
|
1401
|
-
})(p.slug, folderDiv);
|
|
1402
|
-
headerEl.appendChild(chevron);
|
|
1403
|
-
folderDiv.appendChild(headerEl);
|
|
1404
|
-
|
|
1405
|
-
var wtList = document.createElement("div");
|
|
1406
|
-
wtList.className = "mobile-folder-items";
|
|
1407
|
-
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1408
|
-
var isAccessible = worktrees[wi].worktreeAccessible !== false;
|
|
1409
|
-
var wtItem = createMobileProjectItem(worktrees[wi], currentSlug, true);
|
|
1410
|
-
if (!isAccessible) wtItem.classList.add("wt-disabled");
|
|
1411
|
-
if (!isAccessible) {
|
|
1412
|
-
wtItem.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); });
|
|
1413
|
-
}
|
|
1414
|
-
wtList.appendChild(wtItem);
|
|
1415
|
-
}
|
|
1416
|
-
folderDiv.appendChild(wtList);
|
|
1417
|
-
list.appendChild(folderDiv);
|
|
1215
|
+
list.appendChild(createMobileProjectItem(aggregateProjectFamily(p, worktrees), currentSlug));
|
|
1418
1216
|
}
|
|
1419
1217
|
}
|
|
1420
1218
|
|
|
1421
|
-
function createMobileProjectItem(p, currentSlug
|
|
1219
|
+
function createMobileProjectItem(p, currentSlug) {
|
|
1422
1220
|
var el = document.createElement("button");
|
|
1423
|
-
|
|
1221
|
+
var isActive = p.slug === currentSlug;
|
|
1222
|
+
el.className = "mobile-project-item" + (isActive ? " active" : "");
|
|
1223
|
+
if ((!isActive && (p.pendingPermissions > 0 || p.hasFamilyPendingAttention)) || p.hasWorktreePending) {
|
|
1224
|
+
el.classList.add("has-pending-perm");
|
|
1225
|
+
}
|
|
1424
1226
|
|
|
1425
1227
|
var abbrev = document.createElement("span");
|
|
1426
1228
|
abbrev.className = "mobile-project-abbrev";
|
|
@@ -1443,6 +1245,13 @@ function createMobileProjectItem(p, currentSlug, isWorktree) {
|
|
|
1443
1245
|
el.appendChild(dot);
|
|
1444
1246
|
}
|
|
1445
1247
|
|
|
1248
|
+
if (p.unread > 0 && (!isActive || p.hasWorktreeUnread)) {
|
|
1249
|
+
var badge = document.createElement("span");
|
|
1250
|
+
badge.className = "mobile-project-unread";
|
|
1251
|
+
badge.textContent = p.unread > 99 ? "99+" : String(p.unread);
|
|
1252
|
+
el.appendChild(badge);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1446
1255
|
el.addEventListener("click", function () {
|
|
1447
1256
|
if (switchProject) switchProject(p.slug);
|
|
1448
1257
|
if (closeSidebar) closeSidebar();
|
|
@@ -1454,12 +1263,30 @@ function createMobileProjectItem(p, currentSlug, isWorktree) {
|
|
|
1454
1263
|
export function getEmojiCategories() { return EMOJI_CATEGORIES; }
|
|
1455
1264
|
|
|
1456
1265
|
export function updateProjectBadge(slug, count) {
|
|
1457
|
-
var
|
|
1266
|
+
var targetSlug = slug;
|
|
1267
|
+
for (var i = 0; i < cachedProjectList.length; i++) {
|
|
1268
|
+
if (cachedProjectList[i].slug !== slug) continue;
|
|
1269
|
+
cachedProjectList[i].unread = count;
|
|
1270
|
+
if (cachedProjectList[i].isWorktree && cachedProjectList[i].parentSlug) {
|
|
1271
|
+
targetSlug = cachedProjectList[i].parentSlug;
|
|
1272
|
+
}
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
var displayCount = count;
|
|
1277
|
+
var grouped = groupProjects(cachedProjectList);
|
|
1278
|
+
for (var pi = 0; pi < grouped.parents.length; pi++) {
|
|
1279
|
+
if (grouped.parents[pi].slug !== targetSlug) continue;
|
|
1280
|
+
displayCount = aggregateFamily(grouped.parents[pi], grouped.wtByParent[targetSlug] || []).unread;
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
var icon = document.querySelector('.icon-strip-item[data-slug="' + targetSlug + '"]');
|
|
1458
1285
|
if (!icon) return;
|
|
1459
1286
|
var badge = icon.querySelector(".icon-strip-project-badge");
|
|
1460
1287
|
if (!badge) return;
|
|
1461
|
-
if (
|
|
1462
|
-
badge.textContent =
|
|
1288
|
+
if (displayCount > 0) {
|
|
1289
|
+
badge.textContent = displayCount > 99 ? "99+" : String(displayCount);
|
|
1463
1290
|
badge.classList.add("has-unread");
|
|
1464
1291
|
} else {
|
|
1465
1292
|
badge.textContent = "";
|
|
@@ -13,6 +13,9 @@ import { showConfirm } from './app-misc.js';
|
|
|
13
13
|
import { getUpcomingSchedules } from './scheduler.js';
|
|
14
14
|
import { refreshMobileChatSheet } from './sidebar-mobile.js';
|
|
15
15
|
import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES } from './app-rendering.js';
|
|
16
|
+
import { openGroup, separateGroup } from './split-view.js';
|
|
17
|
+
import { groupedSessionIds } from './split-group-helpers.js';
|
|
18
|
+
import { openPairDialog } from './split-pair-ui.js';
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
// --- Session state ---
|
|
@@ -230,6 +233,9 @@ function collectItemSessionIds(item) {
|
|
|
230
233
|
}
|
|
231
234
|
return ids;
|
|
232
235
|
}
|
|
236
|
+
if (item.type === "split-group" && Array.isArray(item.members)) {
|
|
237
|
+
return item.members.map(function (session) { return session.id; });
|
|
238
|
+
}
|
|
233
239
|
return [];
|
|
234
240
|
}
|
|
235
241
|
|
|
@@ -407,6 +413,23 @@ function showNewSessionMenu(anchorBtn, defaultVendor) {
|
|
|
407
413
|
})(VENDOR_ORDER[i]);
|
|
408
414
|
}
|
|
409
415
|
|
|
416
|
+
if (installed.length > 0) {
|
|
417
|
+
var pairSep = document.createElement("div");
|
|
418
|
+
pairSep.className = "session-ctx-sep";
|
|
419
|
+
menu.appendChild(pairSep);
|
|
420
|
+
var pairItem = document.createElement("button");
|
|
421
|
+
pairItem.className = "session-ctx-item";
|
|
422
|
+
pairItem.type = "button";
|
|
423
|
+
pairItem.innerHTML = iconHtml("panel-left-close") + " <span>Pair session…</span>";
|
|
424
|
+
pairItem.title = "Create a Driver and Worker in a split view";
|
|
425
|
+
pairItem.addEventListener("click", function (e) {
|
|
426
|
+
e.stopPropagation();
|
|
427
|
+
closeSessionCtxMenu();
|
|
428
|
+
openPairDialog();
|
|
429
|
+
});
|
|
430
|
+
menu.appendChild(pairItem);
|
|
431
|
+
}
|
|
432
|
+
|
|
410
433
|
// Claude-only launch mode: a bare TUI shell with permission prompts off.
|
|
411
434
|
// Hidden in GUI mode (the default) because the item only ever produces a
|
|
412
435
|
// terminal session, which contradicts what the rest of the menu creates.
|
|
@@ -418,7 +441,7 @@ function showNewSessionMenu(anchorBtn, defaultVendor) {
|
|
|
418
441
|
var skipItem = document.createElement("button");
|
|
419
442
|
skipItem.className = "session-ctx-item";
|
|
420
443
|
skipItem.type = "button";
|
|
421
|
-
skipItem.innerHTML = iconHtml("shield-off") + " <span>Skip
|
|
444
|
+
skipItem.innerHTML = iconHtml("shield-off") + " <span>Skip Permissions (TUI)</span>";
|
|
422
445
|
skipItem.title = "Start a terminal session with --dangerously-skip-permissions";
|
|
423
446
|
skipItem.addEventListener("click", function (e) {
|
|
424
447
|
e.stopPropagation();
|
|
@@ -590,6 +613,7 @@ export function initSidebarSessions() {
|
|
|
590
613
|
if (state.lastVendor !== prev.lastVendor || state.installedVendors !== prev.installedVendors) {
|
|
591
614
|
renderSessionList(null);
|
|
592
615
|
}
|
|
616
|
+
if (state.splitPanes !== prev.splitPanes) renderSessionList(null);
|
|
593
617
|
});
|
|
594
618
|
|
|
595
619
|
// --- Resume session picker ---
|
|
@@ -656,6 +680,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
656
680
|
});
|
|
657
681
|
menu.appendChild(renameItem);
|
|
658
682
|
|
|
683
|
+
|
|
659
684
|
// Session visibility toggle (only the session owner can change)
|
|
660
685
|
if (store.get('isMultiUserMode') && sessionData && sessionData.ownerId && sessionData.ownerId === store.get('myUserId')) {
|
|
661
686
|
var currentVis = (sessionData && sessionData.sessionVisibility) || "shared";
|
|
@@ -705,6 +730,45 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
705
730
|
});
|
|
706
731
|
}
|
|
707
732
|
|
|
733
|
+
function showSplitGroupCtxMenu(anchor, group) {
|
|
734
|
+
closeSessionCtxMenu();
|
|
735
|
+
var menu = document.createElement("div");
|
|
736
|
+
menu.className = "session-ctx-menu";
|
|
737
|
+
|
|
738
|
+
var renameItem = document.createElement("button");
|
|
739
|
+
renameItem.className = "session-ctx-item";
|
|
740
|
+
renameItem.innerHTML = iconHtml("pencil") + " <span>Rename</span>";
|
|
741
|
+
renameItem.addEventListener("click", function (e) {
|
|
742
|
+
e.stopPropagation();
|
|
743
|
+
closeSessionCtxMenu();
|
|
744
|
+
startSplitGroupInlineRename(group);
|
|
745
|
+
});
|
|
746
|
+
menu.appendChild(renameItem);
|
|
747
|
+
|
|
748
|
+
var separateItem = document.createElement("button");
|
|
749
|
+
separateItem.className = "session-ctx-item";
|
|
750
|
+
separateItem.innerHTML = iconHtml("columns-2") + " <span>Separate</span>";
|
|
751
|
+
separateItem.addEventListener("click", function (e) {
|
|
752
|
+
e.stopPropagation();
|
|
753
|
+
closeSessionCtxMenu();
|
|
754
|
+
separateGroup(group);
|
|
755
|
+
});
|
|
756
|
+
menu.appendChild(separateItem);
|
|
757
|
+
|
|
758
|
+
document.body.appendChild(menu);
|
|
759
|
+
sessionCtxMenu = menu;
|
|
760
|
+
refreshIcons();
|
|
761
|
+
requestAnimationFrame(function () {
|
|
762
|
+
var rect = anchor.getBoundingClientRect();
|
|
763
|
+
menu.style.position = "fixed";
|
|
764
|
+
menu.style.top = (rect.bottom + 2) + "px";
|
|
765
|
+
menu.style.right = (window.innerWidth - rect.right) + "px";
|
|
766
|
+
menu.style.left = "auto";
|
|
767
|
+
var menuRect = menu.getBoundingClientRect();
|
|
768
|
+
if (menuRect.bottom > window.innerHeight - 8) menu.style.top = (rect.top - menuRect.height - 2) + "px";
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
708
772
|
function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
|
|
709
773
|
closeSessionCtxMenu();
|
|
710
774
|
|
|
@@ -796,6 +860,40 @@ function startInlineRename(sessionId, currentTitle) {
|
|
|
796
860
|
input.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
797
861
|
}
|
|
798
862
|
|
|
863
|
+
function startSplitGroupInlineRename(group) {
|
|
864
|
+
var el = getSessionListEl().querySelector('.split-group-item[data-split-group-id="' + group.id + '"]');
|
|
865
|
+
if (!el) return;
|
|
866
|
+
var textSpan = el.querySelector(".split-group-name");
|
|
867
|
+
if (!textSpan) return;
|
|
868
|
+
var input = document.createElement("input");
|
|
869
|
+
input.type = "text";
|
|
870
|
+
input.className = "session-rename-input";
|
|
871
|
+
input.value = group.name || "Split";
|
|
872
|
+
var original = textSpan.textContent;
|
|
873
|
+
textSpan.innerHTML = "";
|
|
874
|
+
textSpan.appendChild(input);
|
|
875
|
+
input.focus();
|
|
876
|
+
input.select();
|
|
877
|
+
var finished = false;
|
|
878
|
+
function finish(commit) {
|
|
879
|
+
if (finished) return;
|
|
880
|
+
finished = true;
|
|
881
|
+
var name = input.value.trim();
|
|
882
|
+
if (commit && name && name !== group.name && getWs() && store.get('connected')) {
|
|
883
|
+
getWs().send(JSON.stringify({ type: "split_group_rename", id: group.id, name: name }));
|
|
884
|
+
textSpan.textContent = name;
|
|
885
|
+
} else {
|
|
886
|
+
textSpan.textContent = original;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
input.addEventListener("keydown", function (e) {
|
|
890
|
+
if (e.key === "Enter") { e.preventDefault(); finish(true); }
|
|
891
|
+
if (e.key === "Escape") { e.preventDefault(); finish(false); }
|
|
892
|
+
});
|
|
893
|
+
input.addEventListener("blur", function () { finish(true); });
|
|
894
|
+
input.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
895
|
+
}
|
|
896
|
+
|
|
799
897
|
function startLoopInlineRename(loopId, currentName) {
|
|
800
898
|
var el = getSessionListEl().querySelector('.session-loop-group[data-loop-id="' + loopId + '"]');
|
|
801
899
|
if (!el) return;
|
|
@@ -1207,6 +1305,48 @@ function renderSessionItem(s) {
|
|
|
1207
1305
|
return el;
|
|
1208
1306
|
}
|
|
1209
1307
|
|
|
1308
|
+
function renderSplitGroupItem(group, members) {
|
|
1309
|
+
var el = document.createElement("div");
|
|
1310
|
+
var split = store.get('splitPanes');
|
|
1311
|
+
el.className = "session-item split-group-item" + (split && split.groupId === group.id ? " active" : "");
|
|
1312
|
+
el.dataset.splitGroupId = group.id;
|
|
1313
|
+
|
|
1314
|
+
var text = document.createElement("span");
|
|
1315
|
+
text.className = "session-item-text split-group-text";
|
|
1316
|
+
var html = '<span class="split-group-glyph" aria-hidden="true">' + iconHtml("columns-2") + '</span><span class="split-group-vendors">';
|
|
1317
|
+
for (var i = 0; i < members.length; i++) {
|
|
1318
|
+
var vendor = members[i].vendor || "claude";
|
|
1319
|
+
html += '<img class="session-vendor-icon split-group-vendor" src="' +
|
|
1320
|
+
(VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude) + '" alt="" title="' +
|
|
1321
|
+
escapeHtml(VENDOR_NAMES[vendor] || vendor) + '">';
|
|
1322
|
+
}
|
|
1323
|
+
html += "</span>";
|
|
1324
|
+
if (members[0].isProcessing || members[1].isProcessing) html += '<span class="session-processing"></span>';
|
|
1325
|
+
html += '<span class="split-group-name">' + highlightMatch(group.name || "Split", searchQuery) + "</span>";
|
|
1326
|
+
text.innerHTML = html;
|
|
1327
|
+
el.appendChild(text);
|
|
1328
|
+
|
|
1329
|
+
var unread = (members[0].unread || 0) + (members[1].unread || 0);
|
|
1330
|
+
var badge = document.createElement("span");
|
|
1331
|
+
badge.className = "session-unread-badge" + (unread > 0 ? " has-unread" : "");
|
|
1332
|
+
badge.dataset.splitGroupId = group.id;
|
|
1333
|
+
if (unread > 0) badge.textContent = unread > 99 ? "99+" : String(unread);
|
|
1334
|
+
el.appendChild(badge);
|
|
1335
|
+
|
|
1336
|
+
el.addEventListener("click", function () {
|
|
1337
|
+
if (openGroup(group)) {
|
|
1338
|
+
dismissOverlayPanels();
|
|
1339
|
+
closeSidebar();
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
el.addEventListener("contextmenu", function (e) {
|
|
1343
|
+
e.preventDefault();
|
|
1344
|
+
e.stopPropagation();
|
|
1345
|
+
showSplitGroupCtxMenu(el, group);
|
|
1346
|
+
});
|
|
1347
|
+
return el;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1210
1350
|
// --- Main session list ---
|
|
1211
1351
|
|
|
1212
1352
|
export function renderSessionList(sessions) {
|
|
@@ -1219,10 +1359,15 @@ export function renderSessionList(sessions) {
|
|
|
1219
1359
|
|
|
1220
1360
|
// Partition: loop sessions vs normal sessions
|
|
1221
1361
|
// Group by loopId + date so all runs of the same task on the same day are merged
|
|
1362
|
+
var splitGroups = store.get('splitGroups') || [];
|
|
1363
|
+
var groupedIds = groupedSessionIds(splitGroups);
|
|
1364
|
+
var sessionsById = new Map();
|
|
1365
|
+
for (var si = 0; si < cachedSessions.length; si++) sessionsById.set(cachedSessions[si].id, cachedSessions[si]);
|
|
1222
1366
|
var loopGroups = {}; // groupKey -> [sessions]
|
|
1223
1367
|
var normalSessions = [];
|
|
1224
1368
|
for (var i = 0; i < cachedSessions.length; i++) {
|
|
1225
1369
|
var s = cachedSessions[i];
|
|
1370
|
+
if (groupedIds.has(s.id)) continue;
|
|
1226
1371
|
if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph" && s.loop.source !== "debate") {
|
|
1227
1372
|
// Task crafting sessions live in the scheduler calendar, not the main list (except debate)
|
|
1228
1373
|
continue;
|
|
@@ -1242,6 +1387,18 @@ export function renderSessionList(sessions) {
|
|
|
1242
1387
|
for (var j = 0; j < normalSessions.length; j++) {
|
|
1243
1388
|
items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
|
|
1244
1389
|
}
|
|
1390
|
+
for (var sg = 0; sg < splitGroups.length; sg++) {
|
|
1391
|
+
var splitGroup = splitGroups[sg];
|
|
1392
|
+
var leftMember = sessionsById.get(splitGroup.members[0]);
|
|
1393
|
+
var rightMember = sessionsById.get(splitGroup.members[1]);
|
|
1394
|
+
if (!leftMember || !rightMember) continue;
|
|
1395
|
+
items.push({
|
|
1396
|
+
type: "split-group",
|
|
1397
|
+
group: splitGroup,
|
|
1398
|
+
members: [leftMember, rightMember],
|
|
1399
|
+
lastActivity: Math.max(leftMember.lastActivity || 0, rightMember.lastActivity || 0),
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1245
1402
|
var groupKeys = Object.keys(loopGroups);
|
|
1246
1403
|
for (var k = 0; k < groupKeys.length; k++) {
|
|
1247
1404
|
var gk = groupKeys[k];
|
|
@@ -1265,6 +1422,8 @@ export function renderSessionList(sessions) {
|
|
|
1265
1422
|
if (item.type === "session" && item.data && !isSessionVisibleBySearch(item.data.id)) {
|
|
1266
1423
|
continue;
|
|
1267
1424
|
}
|
|
1425
|
+
if (item.type === "split-group" && searchMatchIds !== null &&
|
|
1426
|
+
!searchMatchIds.has(item.members[0].id) && !searchMatchIds.has(item.members[1].id)) continue;
|
|
1268
1427
|
if (item.type === "session" && item.data && item.data.bookmarked) {
|
|
1269
1428
|
bookmarkedItems.push(item);
|
|
1270
1429
|
} else {
|
|
@@ -1320,6 +1479,8 @@ export function renderSessionList(sessions) {
|
|
|
1320
1479
|
if (loopEl) {
|
|
1321
1480
|
regularContainer.appendChild(loopEl);
|
|
1322
1481
|
}
|
|
1482
|
+
} else if (item.type === "split-group") {
|
|
1483
|
+
regularContainer.appendChild(renderSplitGroupItem(item.group, item.members));
|
|
1323
1484
|
} else {
|
|
1324
1485
|
regularContainer.appendChild(renderSessionItem(item.data));
|
|
1325
1486
|
}
|
|
@@ -1400,7 +1561,20 @@ function renderPresenceAvatars(el, sessionId) {
|
|
|
1400
1561
|
// --- Session badge ---
|
|
1401
1562
|
|
|
1402
1563
|
export function updateSessionBadge(sessionId, count) {
|
|
1564
|
+
for (var i = 0; i < cachedSessions.length; i++) {
|
|
1565
|
+
if (cachedSessions[i].id === sessionId) cachedSessions[i].unread = count;
|
|
1566
|
+
}
|
|
1403
1567
|
var badge = document.querySelector('.session-unread-badge[data-session-id="' + sessionId + '"]');
|
|
1568
|
+
var groups = store.get('splitGroups') || [];
|
|
1569
|
+
for (var gi = 0; gi < groups.length; gi++) {
|
|
1570
|
+
if (groups[gi].members.indexOf(sessionId) === -1) continue;
|
|
1571
|
+
badge = document.querySelector('.session-unread-badge[data-split-group-id="' + groups[gi].id + '"]');
|
|
1572
|
+
count = 0;
|
|
1573
|
+
for (var mi = 0; mi < cachedSessions.length; mi++) {
|
|
1574
|
+
if (groups[gi].members.indexOf(cachedSessions[mi].id) !== -1) count += cachedSessions[mi].unread || 0;
|
|
1575
|
+
}
|
|
1576
|
+
break;
|
|
1577
|
+
}
|
|
1404
1578
|
if (!badge) return;
|
|
1405
1579
|
if (count > 0) {
|
|
1406
1580
|
badge.textContent = count > 99 ? "99+" : String(count);
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getCurrentDmUserId
|
|
13
13
|
} from './sidebar-mates.js';
|
|
14
14
|
import { initSidebarMobile } from './sidebar-mobile.js';
|
|
15
|
+
import { store } from './store.js';
|
|
15
16
|
|
|
16
17
|
var ctx;
|
|
17
18
|
var _syncResizeHandles = null;
|
|
@@ -26,6 +27,7 @@ export function dismissOverlayPanels() {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export function updatePageTitle() {
|
|
30
|
+
if (store.get('paneMode')) return;
|
|
29
31
|
var sessionTitle = "";
|
|
30
32
|
var activeItem = ctx.sessionListEl.querySelector(".session-item.active .session-item-text");
|
|
31
33
|
if (activeItem) sessionTitle = activeItem.textContent;
|