clay-server 2.47.0-beta.4 → 2.47.0-beta.6
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 +5 -25
- package/lib/project-http.js +5 -2
- package/lib/public/app.js +4 -1
- package/lib/public/css/icon-strip.css +0 -190
- package/lib/public/css/mobile-nav.css +20 -0
- package/lib/public/css/sidebar.css +19 -0
- package/lib/public/css/title-bar.css +169 -0
- package/lib/public/index.html +6 -0
- package/lib/public/modules/app-favicon.js +1 -10
- package/lib/public/modules/app-home-hub.js +2 -0
- package/lib/public/modules/app-messages.js +4 -7
- package/lib/public/modules/app-projects.js +43 -30
- package/lib/public/modules/branch-switcher.js +178 -0
- package/lib/public/modules/dom-refs.js +0 -1
- package/lib/public/modules/project-switcher.js +2 -1
- package/lib/public/modules/sidebar-mobile.js +27 -6
- package/lib/public/modules/sidebar-projects.js +63 -236
- package/lib/public/modules/sidebar-sessions.js +5 -0
- package/lib/public/modules/worktree-family.js +60 -0
- package/lib/sessions.js +1 -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 = "";
|
|
@@ -1148,6 +1148,11 @@ function renderSessionItem(s) {
|
|
|
1148
1148
|
var textSpan = document.createElement("span");
|
|
1149
1149
|
textSpan.className = "session-item-text";
|
|
1150
1150
|
var textHtml = "";
|
|
1151
|
+
// Which agent runtime this session ran on. Legacy sessions without a
|
|
1152
|
+
// recorded vendor are Claude (pre-multi-vendor era).
|
|
1153
|
+
var itemVendor = s.vendor || "claude";
|
|
1154
|
+
textHtml += '<img class="session-vendor-icon" src="' + (VENDOR_AVATARS[itemVendor] || VENDOR_AVATARS.claude) +
|
|
1155
|
+
'" alt="" title="' + escapeHtml(VENDOR_NAMES[itemVendor] || itemVendor) + '">';
|
|
1151
1156
|
if (s.isProcessing) {
|
|
1152
1157
|
textHtml += '<span class="session-processing"></span>';
|
|
1153
1158
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Pure helpers for presenting worktree projects as branches of one family.
|
|
2
|
+
|
|
3
|
+
function projectName(project) {
|
|
4
|
+
return project ? (project.title || project.project || project.name || project.slug) : "";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function familyOf(projects, slug) {
|
|
8
|
+
var list = projects || [];
|
|
9
|
+
var current = null;
|
|
10
|
+
for (var i = 0; i < list.length; i++) {
|
|
11
|
+
if (list[i].slug === slug) { current = list[i]; break; }
|
|
12
|
+
}
|
|
13
|
+
if (!current) return { parent: null, worktrees: [] };
|
|
14
|
+
|
|
15
|
+
var parentSlug = current.isWorktree ? current.parentSlug : current.slug;
|
|
16
|
+
var parent = current.isWorktree ? null : current;
|
|
17
|
+
var worktrees = [];
|
|
18
|
+
for (var j = 0; j < list.length; j++) {
|
|
19
|
+
var project = list[j];
|
|
20
|
+
if (!parent && project.slug === parentSlug && !project.isWorktree) parent = project;
|
|
21
|
+
if (project.isWorktree && project.parentSlug === parentSlug) worktrees.push(project);
|
|
22
|
+
}
|
|
23
|
+
if (current.isWorktree && worktrees.indexOf(current) === -1) worktrees.push(current);
|
|
24
|
+
worktrees.sort(function (a, b) { return projectName(a).localeCompare(projectName(b)); });
|
|
25
|
+
return { parent: parent, worktrees: worktrees };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parentProjects(projects) {
|
|
29
|
+
return (projects || []).filter(function (project) { return !project.isWorktree; });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function displayProject(projects, slug) {
|
|
33
|
+
var family = familyOf(projects, slug);
|
|
34
|
+
if (family.parent) return family.parent;
|
|
35
|
+
for (var i = 0; i < (projects || []).length; i++) {
|
|
36
|
+
if (projects[i].slug === slug) return projects[i];
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function aggregateFamily(parent, worktrees) {
|
|
42
|
+
var result = Object.assign({}, parent);
|
|
43
|
+
var children = worktrees || [];
|
|
44
|
+
result.isProcessing = !!result.isProcessing;
|
|
45
|
+
result.unread = result.unread || 0;
|
|
46
|
+
result.pendingPermissions = result.pendingPermissions || 0;
|
|
47
|
+
for (var i = 0; i < children.length; i++) {
|
|
48
|
+
result.isProcessing = result.isProcessing || !!children[i].isProcessing;
|
|
49
|
+
result.unread += children[i].unread || 0;
|
|
50
|
+
result.pendingPermissions += children[i].pendingPermissions || 0;
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function switcherProjectName(projects, project) {
|
|
56
|
+
if (!project || !project.isWorktree) return projectName(project);
|
|
57
|
+
var family = familyOf(projects, project.slug);
|
|
58
|
+
var parentName = family.parent ? projectName(family.parent) : (project.parentSlug || "Project");
|
|
59
|
+
return parentName + " \u2387 " + (project.branch || projectName(project));
|
|
60
|
+
}
|
package/lib/sessions.js
CHANGED
|
@@ -1091,6 +1091,7 @@ function createSessionManager(opts) {
|
|
|
1091
1091
|
searchSessions: searchSessions,
|
|
1092
1092
|
searchSessionContent: searchSessionContent,
|
|
1093
1093
|
setResolveLoopInfo: setResolveLoopInfo,
|
|
1094
|
+
mapSessionForClient: mapSessionForClient,
|
|
1094
1095
|
migrateSessionTitles: migrateSessionTitles,
|
|
1095
1096
|
setSessionVisibility: function (localId, visibility) {
|
|
1096
1097
|
var session = sessions.get(localId);
|
package/package.json
CHANGED