clay-server 3.0.0 → 3.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/mates-prompts.js +3 -2
- package/lib/notes.js +6 -0
- package/lib/project-http.js +3 -6
- package/lib/project-session-notes.js +217 -0
- package/lib/project.js +84 -18
- package/lib/public/app.js +1 -4
- package/lib/public/css/icon-strip.css +190 -0
- package/lib/public/css/messages.css +104 -2
- package/lib/public/css/mobile-nav.css +0 -4
- package/lib/public/css/pane.css +27 -0
- package/lib/public/css/rewind.css +10 -0
- package/lib/public/css/sticky-notes.css +43 -0
- package/lib/public/css/title-bar.css +0 -169
- package/lib/public/index.html +0 -6
- package/lib/public/modules/app-favicon.js +10 -1
- package/lib/public/modules/app-home-hub.js +0 -2
- package/lib/public/modules/app-messages.js +18 -6
- package/lib/public/modules/app-projects.js +30 -43
- package/lib/public/modules/app-rendering.js +42 -3
- package/lib/public/modules/dom-refs.js +1 -0
- package/lib/public/modules/input.js +13 -0
- package/lib/public/modules/project-switcher.js +1 -2
- package/lib/public/modules/sidebar-mobile.js +6 -19
- package/lib/public/modules/sidebar-projects.js +232 -63
- package/lib/public/modules/split-pair-ui.js +30 -0
- package/lib/public/modules/sticky-note-markdown.js +89 -0
- package/lib/public/modules/sticky-notes.js +56 -103
- package/lib/sdk-bridge.js +25 -1
- package/lib/session-notes-mcp-server.js +39 -0
- package/lib/ws-schema.js +1 -0
- package/lib/yoke/adapters/codex.js +44 -8
- package/lib/yoke/mcp-bridge-server.js +7 -7
- package/package.json +1 -1
- package/lib/public/modules/branch-switcher.js +0 -178
- package/lib/public/modules/worktree-family.js +0 -60
|
@@ -10,10 +10,9 @@ 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 } from './app-projects.js';
|
|
13
|
+
import { switchProject, openAddProjectModal, getCachedProjects } 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';
|
|
17
16
|
|
|
18
17
|
// --- Project state ---
|
|
19
18
|
var cachedProjectList = [];
|
|
@@ -32,6 +31,9 @@ var emojiPickerEl = null;
|
|
|
32
31
|
var draggedSlug = null;
|
|
33
32
|
var draggedEl = null;
|
|
34
33
|
|
|
34
|
+
// --- Worktree folder collapse state (session-local) ---
|
|
35
|
+
var wtCollapsed = {};
|
|
36
|
+
|
|
35
37
|
var EMOJI_CATEGORIES = [
|
|
36
38
|
{ id: "frequent", icon: "🕐", label: "Frequent", emojis: [
|
|
37
39
|
"😀","😎","🤓","🧠","💡","🔥","⚡","🚀",
|
|
@@ -519,8 +521,17 @@ function showIconCtxMenu(anchorEl, slug, name) {
|
|
|
519
521
|
}
|
|
520
522
|
});
|
|
521
523
|
menu.appendChild(removeWtItem);
|
|
524
|
+
} else {
|
|
525
|
+
var wtItem = document.createElement("button");
|
|
526
|
+
wtItem.className = "project-ctx-item";
|
|
527
|
+
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
528
|
+
wtItem.addEventListener("click", function (e) {
|
|
529
|
+
e.stopPropagation();
|
|
530
|
+
closeProjectCtxMenu();
|
|
531
|
+
showWorktreeModal(slug, name || slug);
|
|
532
|
+
});
|
|
533
|
+
menu.appendChild(wtItem);
|
|
522
534
|
}
|
|
523
|
-
// Worktree creation lives in the title-bar branch chip, not here.
|
|
524
535
|
|
|
525
536
|
document.body.appendChild(menu);
|
|
526
537
|
projectCtxMenu = menu;
|
|
@@ -605,7 +616,20 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
605
616
|
}
|
|
606
617
|
}
|
|
607
618
|
|
|
608
|
-
|
|
619
|
+
var sep2 = document.createElement("div");
|
|
620
|
+
sep2.className = "project-ctx-separator";
|
|
621
|
+
menu.appendChild(sep2);
|
|
622
|
+
|
|
623
|
+
// --- Add Worktree ---
|
|
624
|
+
var wtItem = document.createElement("button");
|
|
625
|
+
wtItem.className = "project-ctx-item";
|
|
626
|
+
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
627
|
+
wtItem.addEventListener("click", function (e) {
|
|
628
|
+
e.stopPropagation();
|
|
629
|
+
closeProjectCtxMenu();
|
|
630
|
+
showWorktreeModal(slug, name || slug);
|
|
631
|
+
});
|
|
632
|
+
menu.appendChild(wtItem);
|
|
609
633
|
|
|
610
634
|
if (!store.get('permissions') || store.get('permissions').deleteProject !== false) {
|
|
611
635
|
var sep3 = document.createElement("div");
|
|
@@ -926,6 +950,12 @@ function setupDragHandlers(el, slug) {
|
|
|
926
950
|
});
|
|
927
951
|
}
|
|
928
952
|
|
|
953
|
+
// --- Worktree folder collapse ---
|
|
954
|
+
|
|
955
|
+
function setWtCollapsed(slug, collapsed) {
|
|
956
|
+
wtCollapsed[slug] = collapsed;
|
|
957
|
+
}
|
|
958
|
+
|
|
929
959
|
function groupProjects(projects) {
|
|
930
960
|
var parents = [];
|
|
931
961
|
var wtByParent = {};
|
|
@@ -941,21 +971,6 @@ function groupProjects(projects) {
|
|
|
941
971
|
return { parents: parents, wtByParent: wtByParent };
|
|
942
972
|
}
|
|
943
973
|
|
|
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
|
-
|
|
959
974
|
// --- Icon item creation ---
|
|
960
975
|
|
|
961
976
|
function createIconItem(p, currentSlug) {
|
|
@@ -987,7 +1002,7 @@ function createIconItem(p, currentSlug) {
|
|
|
987
1002
|
|
|
988
1003
|
var projectBadge = document.createElement("span");
|
|
989
1004
|
projectBadge.className = "icon-strip-project-badge";
|
|
990
|
-
if (p.unread > 0 &&
|
|
1005
|
+
if (p.unread > 0 && !isActive) {
|
|
991
1006
|
projectBadge.textContent = p.unread > 99 ? "99+" : String(p.unread);
|
|
992
1007
|
projectBadge.classList.add("has-unread");
|
|
993
1008
|
}
|
|
@@ -997,8 +1012,7 @@ function createIconItem(p, currentSlug) {
|
|
|
997
1012
|
// broadcast — they surface via tui_attention notifications. We shake the
|
|
998
1013
|
// same icon for either, so the user notices regardless of which engine
|
|
999
1014
|
// is asking.
|
|
1000
|
-
if (
|
|
1001
|
-
p.hasWorktreePending) {
|
|
1015
|
+
if (!isActive && (p.pendingPermissions > 0 || getPendingTuiAttention(p.slug) > 0)) {
|
|
1002
1016
|
el.classList.add("has-pending-perm");
|
|
1003
1017
|
}
|
|
1004
1018
|
|
|
@@ -1019,7 +1033,7 @@ function createIconItem(p, currentSlug) {
|
|
|
1019
1033
|
|
|
1020
1034
|
// --- Worktree creation modal ---
|
|
1021
1035
|
|
|
1022
|
-
|
|
1036
|
+
function showWorktreeModal(parentSlug, parentName) {
|
|
1023
1037
|
var existing = document.getElementById("wt-modal-container");
|
|
1024
1038
|
if (existing) existing.remove();
|
|
1025
1039
|
|
|
@@ -1174,17 +1188,164 @@ export function renderIconStrip(projects, currentSlug) {
|
|
|
1174
1188
|
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1175
1189
|
var p = grouped.parents[i];
|
|
1176
1190
|
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1177
|
-
var
|
|
1178
|
-
|
|
1191
|
+
var hasWorktrees = worktrees.length > 0;
|
|
1192
|
+
|
|
1193
|
+
if (!hasWorktrees) {
|
|
1194
|
+
var el = createIconItem(p, currentSlug);
|
|
1195
|
+
(function (slug, name, elem) {
|
|
1196
|
+
elem.addEventListener("contextmenu", function (e) {
|
|
1197
|
+
e.preventDefault();
|
|
1198
|
+
e.stopPropagation();
|
|
1199
|
+
showIconCtxMenu(elem, slug, name);
|
|
1200
|
+
});
|
|
1201
|
+
})(p.slug, p.name || p.slug, el);
|
|
1202
|
+
setupDragHandlers(el, p.slug);
|
|
1203
|
+
container.appendChild(el);
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Folder group for parent + worktrees
|
|
1208
|
+
var folder = document.createElement("div");
|
|
1209
|
+
folder.className = "icon-strip-group";
|
|
1210
|
+
folder.dataset.parentSlug = p.slug;
|
|
1211
|
+
if (wtCollapsed[p.slug]) folder.classList.add("collapsed");
|
|
1212
|
+
|
|
1213
|
+
if (!p.isProcessing) {
|
|
1214
|
+
for (var wpi = 0; wpi < worktrees.length; wpi++) {
|
|
1215
|
+
if (worktrees[wpi].isProcessing) { p.isProcessing = true; break; }
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
var header = createIconItem(p, currentSlug);
|
|
1220
|
+
header.classList.add("folder-header");
|
|
1179
1221
|
(function (slug, name, elem) {
|
|
1180
1222
|
elem.addEventListener("contextmenu", function (e) {
|
|
1181
1223
|
e.preventDefault();
|
|
1182
1224
|
e.stopPropagation();
|
|
1183
1225
|
showIconCtxMenu(elem, slug, name);
|
|
1184
1226
|
});
|
|
1185
|
-
})(p.slug, p.name || p.slug,
|
|
1186
|
-
setupDragHandlers(
|
|
1187
|
-
|
|
1227
|
+
})(p.slug, p.name || p.slug, header);
|
|
1228
|
+
setupDragHandlers(header, p.slug);
|
|
1229
|
+
|
|
1230
|
+
var chevron = document.createElement("span");
|
|
1231
|
+
chevron.className = "icon-strip-group-chevron";
|
|
1232
|
+
chevron.innerHTML = '<i data-lucide="git-branch"></i>';
|
|
1233
|
+
(function (parentSlug, folderEl) {
|
|
1234
|
+
chevron.addEventListener("click", function (e) {
|
|
1235
|
+
e.preventDefault();
|
|
1236
|
+
e.stopPropagation();
|
|
1237
|
+
var nowCollapsed = folderEl.classList.toggle("collapsed");
|
|
1238
|
+
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1239
|
+
});
|
|
1240
|
+
chevron.addEventListener("contextmenu", function (e) {
|
|
1241
|
+
e.preventDefault();
|
|
1242
|
+
e.stopPropagation();
|
|
1243
|
+
});
|
|
1244
|
+
})(p.slug, folder);
|
|
1245
|
+
chevron.setAttribute("data-tip", "Toggle worktrees");
|
|
1246
|
+
header.appendChild(chevron);
|
|
1247
|
+
folder.appendChild(header);
|
|
1248
|
+
|
|
1249
|
+
var itemsContainer = document.createElement("div");
|
|
1250
|
+
itemsContainer.className = "icon-strip-group-items";
|
|
1251
|
+
|
|
1252
|
+
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1253
|
+
(function (wt) {
|
|
1254
|
+
var wtEl = document.createElement("a");
|
|
1255
|
+
var isWtActive = wt.slug === currentSlug && !currentDmUserId;
|
|
1256
|
+
var isAccessible = wt.worktreeAccessible !== false;
|
|
1257
|
+
wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (!isAccessible ? " wt-disabled" : "");
|
|
1258
|
+
wtEl.href = "/p/" + wt.slug + "/";
|
|
1259
|
+
wtEl.dataset.slug = wt.slug;
|
|
1260
|
+
|
|
1261
|
+
if (wt.icon) {
|
|
1262
|
+
var wtEmoji = document.createElement("span");
|
|
1263
|
+
wtEmoji.className = "wt-branch-abbrev project-emoji";
|
|
1264
|
+
wtEmoji.textContent = wt.icon;
|
|
1265
|
+
parseEmojis(wtEmoji);
|
|
1266
|
+
wtEl.appendChild(wtEmoji);
|
|
1267
|
+
} else {
|
|
1268
|
+
var abbrev = document.createElement("span");
|
|
1269
|
+
abbrev.className = "wt-branch-abbrev";
|
|
1270
|
+
abbrev.textContent = getProjectAbbrev(wt.name);
|
|
1271
|
+
wtEl.appendChild(abbrev);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
var wtStatus = document.createElement("span");
|
|
1275
|
+
wtStatus.className = "icon-strip-status";
|
|
1276
|
+
if (wt.isProcessing) wtStatus.classList.add("processing");
|
|
1277
|
+
wtEl.appendChild(wtStatus);
|
|
1278
|
+
|
|
1279
|
+
var tooltipText = wt.name;
|
|
1280
|
+
if (!isAccessible) {
|
|
1281
|
+
tooltipText += " (outside project path, cannot be accessed)";
|
|
1282
|
+
}
|
|
1283
|
+
(function (text, elem) {
|
|
1284
|
+
elem.addEventListener("mouseenter", function () { showIconTooltip(elem, text); });
|
|
1285
|
+
elem.addEventListener("mouseleave", hideIconTooltip);
|
|
1286
|
+
})(tooltipText, wtEl);
|
|
1287
|
+
|
|
1288
|
+
if (isAccessible) {
|
|
1289
|
+
(function (slug) {
|
|
1290
|
+
wtEl.addEventListener("click", function (e) {
|
|
1291
|
+
e.preventDefault();
|
|
1292
|
+
if (switchProject) switchProject(slug);
|
|
1293
|
+
});
|
|
1294
|
+
})(wt.slug);
|
|
1295
|
+
} else {
|
|
1296
|
+
wtEl.addEventListener("click", function (e) {
|
|
1297
|
+
e.preventDefault();
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (isAccessible) {
|
|
1302
|
+
(function (slug, name, elem) {
|
|
1303
|
+
elem.addEventListener("contextmenu", function (e) {
|
|
1304
|
+
e.preventDefault();
|
|
1305
|
+
e.stopPropagation();
|
|
1306
|
+
showIconCtxMenu(elem, slug, name);
|
|
1307
|
+
});
|
|
1308
|
+
})(wt.slug, wt.name, wtEl);
|
|
1309
|
+
} else {
|
|
1310
|
+
wtEl.addEventListener("contextmenu", function (e) {
|
|
1311
|
+
e.preventDefault();
|
|
1312
|
+
e.stopPropagation();
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (!isWtActive && (wt.pendingPermissions > 0 || getPendingTuiAttention(wt.slug) > 0)) {
|
|
1317
|
+
wtEl.classList.add("has-pending-perm");
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
itemsContainer.appendChild(wtEl);
|
|
1321
|
+
})(worktrees[wi]);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
var hasWtPendingPerm = false;
|
|
1325
|
+
for (var wpi2 = 0; wpi2 < worktrees.length; wpi2++) {
|
|
1326
|
+
if (worktrees[wpi2].pendingPermissions > 0 || getPendingTuiAttention(worktrees[wpi2].slug) > 0) {
|
|
1327
|
+
hasWtPendingPerm = true;
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (hasWtPendingPerm) folder.classList.remove("collapsed");
|
|
1332
|
+
|
|
1333
|
+
var addBtn = document.createElement("button");
|
|
1334
|
+
addBtn.className = "icon-strip-group-add";
|
|
1335
|
+
addBtn.textContent = "+";
|
|
1336
|
+
(function (parentSlug, parentName, btn) {
|
|
1337
|
+
btn.addEventListener("click", function (e) {
|
|
1338
|
+
e.preventDefault();
|
|
1339
|
+
e.stopPropagation();
|
|
1340
|
+
showWorktreeModal(parentSlug, parentName);
|
|
1341
|
+
});
|
|
1342
|
+
btn.addEventListener("mouseenter", function () { showIconTooltip(btn, "New worktree"); });
|
|
1343
|
+
btn.addEventListener("mouseleave", hideIconTooltip);
|
|
1344
|
+
})(p.slug, p.name, addBtn);
|
|
1345
|
+
itemsContainer.appendChild(addBtn);
|
|
1346
|
+
|
|
1347
|
+
folder.appendChild(itemsContainer);
|
|
1348
|
+
container.appendChild(folder);
|
|
1188
1349
|
}
|
|
1189
1350
|
|
|
1190
1351
|
// Update home icon active state
|
|
@@ -1212,17 +1373,50 @@ function renderProjectList(projects, currentSlug) {
|
|
|
1212
1373
|
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1213
1374
|
var p = grouped.parents[i];
|
|
1214
1375
|
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1215
|
-
|
|
1376
|
+
|
|
1377
|
+
if (worktrees.length === 0) {
|
|
1378
|
+
list.appendChild(createMobileProjectItem(p, currentSlug, false));
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
var folderDiv = document.createElement("div");
|
|
1383
|
+
folderDiv.className = "mobile-project-folder";
|
|
1384
|
+
if (wtCollapsed[p.slug]) folderDiv.classList.add("collapsed");
|
|
1385
|
+
|
|
1386
|
+
var headerEl = createMobileProjectItem(p, currentSlug, false);
|
|
1387
|
+
var chevron = document.createElement("span");
|
|
1388
|
+
chevron.className = "mobile-folder-chevron";
|
|
1389
|
+
chevron.innerHTML = "▼";
|
|
1390
|
+
(function (parentSlug, fDiv) {
|
|
1391
|
+
chevron.addEventListener("click", function (e) {
|
|
1392
|
+
e.preventDefault();
|
|
1393
|
+
e.stopPropagation();
|
|
1394
|
+
var nowCollapsed = fDiv.classList.toggle("collapsed");
|
|
1395
|
+
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1396
|
+
});
|
|
1397
|
+
})(p.slug, folderDiv);
|
|
1398
|
+
headerEl.appendChild(chevron);
|
|
1399
|
+
folderDiv.appendChild(headerEl);
|
|
1400
|
+
|
|
1401
|
+
var wtList = document.createElement("div");
|
|
1402
|
+
wtList.className = "mobile-folder-items";
|
|
1403
|
+
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1404
|
+
var isAccessible = worktrees[wi].worktreeAccessible !== false;
|
|
1405
|
+
var wtItem = createMobileProjectItem(worktrees[wi], currentSlug, true);
|
|
1406
|
+
if (!isAccessible) wtItem.classList.add("wt-disabled");
|
|
1407
|
+
if (!isAccessible) {
|
|
1408
|
+
wtItem.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); });
|
|
1409
|
+
}
|
|
1410
|
+
wtList.appendChild(wtItem);
|
|
1411
|
+
}
|
|
1412
|
+
folderDiv.appendChild(wtList);
|
|
1413
|
+
list.appendChild(folderDiv);
|
|
1216
1414
|
}
|
|
1217
1415
|
}
|
|
1218
1416
|
|
|
1219
|
-
function createMobileProjectItem(p, currentSlug) {
|
|
1417
|
+
function createMobileProjectItem(p, currentSlug, isWorktree) {
|
|
1220
1418
|
var el = document.createElement("button");
|
|
1221
|
-
|
|
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
|
-
}
|
|
1419
|
+
el.className = "mobile-project-item" + (p.slug === currentSlug ? " active" : "") + (isWorktree ? " wt-item" : "");
|
|
1226
1420
|
|
|
1227
1421
|
var abbrev = document.createElement("span");
|
|
1228
1422
|
abbrev.className = "mobile-project-abbrev";
|
|
@@ -1245,13 +1439,6 @@ function createMobileProjectItem(p, currentSlug) {
|
|
|
1245
1439
|
el.appendChild(dot);
|
|
1246
1440
|
}
|
|
1247
1441
|
|
|
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
|
-
|
|
1255
1442
|
el.addEventListener("click", function () {
|
|
1256
1443
|
if (switchProject) switchProject(p.slug);
|
|
1257
1444
|
if (closeSidebar) closeSidebar();
|
|
@@ -1263,30 +1450,12 @@ function createMobileProjectItem(p, currentSlug) {
|
|
|
1263
1450
|
export function getEmojiCategories() { return EMOJI_CATEGORIES; }
|
|
1264
1451
|
|
|
1265
1452
|
export function updateProjectBadge(slug, count) {
|
|
1266
|
-
var
|
|
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 + '"]');
|
|
1453
|
+
var icon = document.querySelector('.icon-strip-item[data-slug="' + slug + '"]');
|
|
1285
1454
|
if (!icon) return;
|
|
1286
1455
|
var badge = icon.querySelector(".icon-strip-project-badge");
|
|
1287
1456
|
if (!badge) return;
|
|
1288
|
-
if (
|
|
1289
|
-
badge.textContent =
|
|
1457
|
+
if (count > 0) {
|
|
1458
|
+
badge.textContent = count > 99 ? "99+" : String(count);
|
|
1290
1459
|
badge.classList.add("has-unread");
|
|
1291
1460
|
} else {
|
|
1292
1461
|
badge.textContent = "";
|
|
@@ -251,11 +251,41 @@ export function handlePairCreated(msg) {
|
|
|
251
251
|
return msg.group || null;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// --- Worker-pane composer notice -------------------------------------
|
|
255
|
+
// Shown inside a pane iframe while ITS session is processing a delegated
|
|
256
|
+
// turn: typing is allowed (queueing is correct behavior), the bar just
|
|
257
|
+
// makes it visible that a new message joins the Driver's turn.
|
|
258
|
+
var workerNoticeEl = null;
|
|
259
|
+
|
|
260
|
+
export function showWorkerDelegationNotice(driverTitle) {
|
|
261
|
+
if (!store.get('paneMode')) return;
|
|
262
|
+
var inputArea = document.getElementById("input-area");
|
|
263
|
+
if (!inputArea || !inputArea.parentNode) return;
|
|
264
|
+
if (!workerNoticeEl) {
|
|
265
|
+
workerNoticeEl = document.createElement("div");
|
|
266
|
+
workerNoticeEl.className = "pane-delegation-notice";
|
|
267
|
+
inputArea.parentNode.insertBefore(workerNoticeEl, inputArea);
|
|
268
|
+
}
|
|
269
|
+
var who = driverTitle ? "“" + driverTitle + "”" : "the Driver";
|
|
270
|
+
workerNoticeEl.textContent = "Following an instruction from " + who + " — messages you send now join the same turn.";
|
|
271
|
+
workerNoticeEl.classList.add("visible");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function hideWorkerDelegationNotice() {
|
|
275
|
+
if (workerNoticeEl) workerNoticeEl.classList.remove("visible");
|
|
276
|
+
}
|
|
277
|
+
|
|
254
278
|
export function handleSplitDelegation(msg) {
|
|
255
279
|
var states = Object.assign({}, store.get('splitDelegations') || {});
|
|
256
280
|
if (msg.active) states[msg.groupId] = msg;
|
|
257
281
|
else delete states[msg.groupId];
|
|
258
282
|
store.set({ splitDelegations: states });
|
|
283
|
+
// Inside the worker's own pane, mirror the delegation into the composer
|
|
284
|
+
// notice (the delegated user_message shows it too; this covers the end).
|
|
285
|
+
if (store.get('paneMode') && msg.to === store.get('activeSessionId')) {
|
|
286
|
+
if (msg.active) showWorkerDelegationNotice(null);
|
|
287
|
+
else hideWorkerDelegationNotice();
|
|
288
|
+
}
|
|
259
289
|
}
|
|
260
290
|
|
|
261
291
|
function sendSetPair(groupId, driverId) {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Markdown helpers for sticky-note titles, handoff bodies, and checklists.
|
|
2
|
+
|
|
3
|
+
export function getTitle(text) {
|
|
4
|
+
if (!text) return "";
|
|
5
|
+
var idx = text.indexOf("\n");
|
|
6
|
+
return idx === -1 ? text : text.substring(0, idx);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatInline(text) {
|
|
10
|
+
var escaped = text
|
|
11
|
+
.replace(/&/g, "&")
|
|
12
|
+
.replace(/</g, "<")
|
|
13
|
+
.replace(/>/g, ">");
|
|
14
|
+
return escaped
|
|
15
|
+
.replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>')
|
|
16
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
17
|
+
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<em>$1</em>")
|
|
18
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
19
|
+
.replace(/~~(.+?)~~/g, "<del>$1</del>")
|
|
20
|
+
.replace(/^(?:-\s*)?\[[xX]\]/gm, '<span class="sn-check checked" role="checkbox" aria-checked="true" tabindex="0" contenteditable="false">✓</span>')
|
|
21
|
+
.replace(/^(?:-\s*)?\[ \]/gm, '<span class="sn-check" role="checkbox" aria-checked="false" tabindex="0" contenteditable="false">☐</span>')
|
|
22
|
+
.replace(/\n/g, "<br>");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function renderMiniMarkdown(text) {
|
|
26
|
+
if (!text) return "";
|
|
27
|
+
var lines = text.split("\n");
|
|
28
|
+
var title = lines[0];
|
|
29
|
+
var body = lines.slice(1).join("\n");
|
|
30
|
+
var html = '<div class="sn-title">' + formatInline(title) + "</div>";
|
|
31
|
+
if (body.trim()) html += formatInline(body);
|
|
32
|
+
return html;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function childrenToMarkdown(element) {
|
|
36
|
+
var result = "";
|
|
37
|
+
for (var i = 0; i < element.childNodes.length; i++) {
|
|
38
|
+
result += nodeToMarkdown(element.childNodes[i]);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function nodeToMarkdown(node) {
|
|
44
|
+
if (node.nodeType === 3) return node.textContent;
|
|
45
|
+
if (node.nodeType !== 1) return "";
|
|
46
|
+
|
|
47
|
+
var tag = node.tagName;
|
|
48
|
+
var inner = childrenToMarkdown(node);
|
|
49
|
+
switch (tag) {
|
|
50
|
+
case "STRONG": case "B": return "**" + inner + "**";
|
|
51
|
+
case "EM": case "I": return "*" + inner + "*";
|
|
52
|
+
case "DEL": case "S": case "STRIKE": return "~~" + inner + "~~";
|
|
53
|
+
case "CODE": return "`" + inner + "`";
|
|
54
|
+
case "BR": return "\n";
|
|
55
|
+
case "DIV":
|
|
56
|
+
if (node.classList.contains("sn-title")) return inner;
|
|
57
|
+
if (node.classList.contains("sn-placeholder")) return "";
|
|
58
|
+
return "\n" + inner;
|
|
59
|
+
case "P": return "\n" + inner;
|
|
60
|
+
case "A": return node.getAttribute("href") || inner;
|
|
61
|
+
case "SPAN":
|
|
62
|
+
if (node.classList.contains("sn-check")) {
|
|
63
|
+
return node.classList.contains("checked") ? "- [x]" : "- [ ]";
|
|
64
|
+
}
|
|
65
|
+
if (node.classList.contains("sn-placeholder")) return "";
|
|
66
|
+
return inner;
|
|
67
|
+
default: return inner;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function extractMarkdown(rendered) {
|
|
72
|
+
var titleEl = rendered.querySelector(".sn-title");
|
|
73
|
+
if (titleEl) {
|
|
74
|
+
var titleMarkdown = childrenToMarkdown(titleEl);
|
|
75
|
+
var rest = "";
|
|
76
|
+
var afterTitle = false;
|
|
77
|
+
for (var i = 0; i < rendered.childNodes.length; i++) {
|
|
78
|
+
var child = rendered.childNodes[i];
|
|
79
|
+
if (child === titleEl) {
|
|
80
|
+
afterTitle = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (afterTitle) rest += nodeToMarkdown(child);
|
|
84
|
+
}
|
|
85
|
+
if (rest && rest.charAt(0) === "\n") rest = rest.substring(1);
|
|
86
|
+
return titleMarkdown + (rest ? "\n" + rest : "");
|
|
87
|
+
}
|
|
88
|
+
return childrenToMarkdown(rendered).replace(/^\n+/, "");
|
|
89
|
+
}
|