clay-server 3.0.0 → 3.1.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/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 +18 -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-notes.js +21 -0
- 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
|
@@ -430,6 +430,35 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName, d
|
|
|
430
430
|
bubble.className = "bubble";
|
|
431
431
|
bubble.dir = "auto";
|
|
432
432
|
|
|
433
|
+
// Delegated work orders render as a self-contained card: identity lives
|
|
434
|
+
// in an in-card header strip (the generic bubble avatar/header are hidden
|
|
435
|
+
// via CSS) and the body is clean markdown on a neutral background.
|
|
436
|
+
if (isDelegated) {
|
|
437
|
+
var cardHead = document.createElement("div");
|
|
438
|
+
cardHead.className = "delegated-card-head";
|
|
439
|
+
var cardAvi = document.createElement("img");
|
|
440
|
+
cardAvi.className = "delegated-card-avatar";
|
|
441
|
+
cardAvi.src = VENDOR_AVATARS[delegatedMeta.delegatedByVendor] || VENDOR_AVATARS.claude;
|
|
442
|
+
cardAvi.alt = "";
|
|
443
|
+
cardHead.appendChild(cardAvi);
|
|
444
|
+
var cardHeadText = document.createElement("div");
|
|
445
|
+
cardHeadText.className = "delegated-card-headtext";
|
|
446
|
+
var eyebrow = document.createElement("span");
|
|
447
|
+
eyebrow.className = "delegated-card-eyebrow";
|
|
448
|
+
eyebrow.textContent = "Delegated task";
|
|
449
|
+
cardHeadText.appendChild(eyebrow);
|
|
450
|
+
var cardFrom = document.createElement("span");
|
|
451
|
+
cardFrom.className = "delegated-card-from";
|
|
452
|
+
cardFrom.textContent = delegatedMeta.delegatedByTitle || "Driver";
|
|
453
|
+
cardHeadText.appendChild(cardFrom);
|
|
454
|
+
cardHead.appendChild(cardHeadText);
|
|
455
|
+
var cardTime = document.createElement("span");
|
|
456
|
+
cardTime.className = "delegated-card-time";
|
|
457
|
+
cardTime.textContent = getMsgTime();
|
|
458
|
+
cardHead.appendChild(cardTime);
|
|
459
|
+
bubble.appendChild(cardHead);
|
|
460
|
+
}
|
|
461
|
+
|
|
433
462
|
if (images && images.length > 0) {
|
|
434
463
|
var imgRow = document.createElement("div");
|
|
435
464
|
imgRow.className = "bubble-images";
|
|
@@ -475,9 +504,19 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName, d
|
|
|
475
504
|
}
|
|
476
505
|
|
|
477
506
|
if (text) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
507
|
+
if (isDelegated) {
|
|
508
|
+
// Delegated instructions are agent-authored work orders: render them
|
|
509
|
+
// as markdown (headings, lists, code) instead of a plain text blob.
|
|
510
|
+
var mdEl = document.createElement("div");
|
|
511
|
+
mdEl.className = "md-content delegated-brief";
|
|
512
|
+
mdEl.innerHTML = renderMarkdown(text);
|
|
513
|
+
highlightCodeBlocks(mdEl);
|
|
514
|
+
bubble.appendChild(mdEl);
|
|
515
|
+
} else {
|
|
516
|
+
var textEl = document.createElement("span");
|
|
517
|
+
textEl.textContent = text;
|
|
518
|
+
bubble.appendChild(textEl);
|
|
519
|
+
}
|
|
481
520
|
}
|
|
482
521
|
|
|
483
522
|
|
|
@@ -16,5 +16,6 @@ export function getSessionListEl() { return ref("session-list"); }
|
|
|
16
16
|
|
|
17
17
|
export function getStatusDot() {
|
|
18
18
|
return document.querySelector("#icon-strip-projects .icon-strip-item.active .icon-strip-status") ||
|
|
19
|
+
document.querySelector("#icon-strip-projects .icon-strip-wt-item.active .icon-strip-status") ||
|
|
19
20
|
document.querySelector("#icon-strip-users .icon-strip-mate.active .icon-strip-status");
|
|
20
21
|
}
|
|
@@ -6,6 +6,7 @@ import { store } from './store.js';
|
|
|
6
6
|
import { mateAvatarUrl } from './avatar.js';
|
|
7
7
|
import { tuiIsActive, tuiSubmitText } from './session-tui-view.js';
|
|
8
8
|
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
9
|
+
import { showToast } from './utils.js';
|
|
9
10
|
|
|
10
11
|
var ctx;
|
|
11
12
|
|
|
@@ -284,6 +285,18 @@ export function sendMessage() {
|
|
|
284
285
|
if (_selVendor) payload.vendor = _selVendor;
|
|
285
286
|
ctx.ws.send(JSON.stringify(payload));
|
|
286
287
|
|
|
288
|
+
// A turn blocked on an unanswered tool-permission request looks like a
|
|
289
|
+
// black hole: the new message queues silently while the dots keep
|
|
290
|
+
// bouncing, so users re-send the same text (and both copies land once
|
|
291
|
+
// the permission is answered). Point them at the pending card instead.
|
|
292
|
+
var pendingPerm = document.querySelector("#messages .permission-container:not(.resolved)");
|
|
293
|
+
if (pendingPerm) {
|
|
294
|
+
pendingPerm.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
295
|
+
pendingPerm.classList.add("permission-attention");
|
|
296
|
+
setTimeout(function () { pendingPerm.classList.remove("permission-attention"); }, 2600);
|
|
297
|
+
showToast("A tool permission request is still waiting — your message is queued until you answer it.", "error");
|
|
298
|
+
}
|
|
299
|
+
|
|
287
300
|
// First message commits the vendor and bumps the session into
|
|
288
301
|
// has-history state. The server won't re-fire session_switched after a
|
|
289
302
|
// message, so mirror the vendor-toggle/active-indicator swap locally:
|
|
@@ -19,7 +19,6 @@ import { getCachedProjects, switchProject } from './app-projects.js';
|
|
|
19
19
|
import { openDm } from './app-dm.js';
|
|
20
20
|
import { mateAvatarUrl } from './avatar.js';
|
|
21
21
|
import { parseEmojis } from './markdown.js';
|
|
22
|
-
import { switcherProjectName } from './worktree-family.js';
|
|
23
22
|
|
|
24
23
|
var _projectMru = []; // project slugs, most recent first
|
|
25
24
|
var _mateMru = []; // mate ids (mate_XXX), most recent first
|
|
@@ -264,7 +263,7 @@ function buildProjectEntries() {
|
|
|
264
263
|
var unreachable = p.isWorktree && p.worktreeAccessible === false;
|
|
265
264
|
return {
|
|
266
265
|
key: p.slug,
|
|
267
|
-
title:
|
|
266
|
+
title: p.title || p.project || p.slug,
|
|
268
267
|
icon: p.icon || null,
|
|
269
268
|
fallbackIcon: 'folder',
|
|
270
269
|
isCurrent: p.slug === store.get('currentSlug'),
|
|
@@ -38,7 +38,6 @@ import { showHomeHub } from './app-home-hub.js';
|
|
|
38
38
|
import { openTerminal } from './terminal.js';
|
|
39
39
|
import { requestKnowledgeList } from './mate-knowledge.js';
|
|
40
40
|
import { loadRootDirectory } from './filebrowser.js';
|
|
41
|
-
import { aggregateFamily, familyOf, parentProjects } from './worktree-family.js';
|
|
42
41
|
|
|
43
42
|
// --- Mobile state ---
|
|
44
43
|
var mobileChatSheetOpen = false;
|
|
@@ -148,22 +147,10 @@ function closeMobileSheet() {
|
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
function renderSheetProjects(listEl) {
|
|
151
|
-
var
|
|
152
|
-
var parents = parentProjects(projects);
|
|
153
|
-
var activeFamily = familyOf(projects, getCachedCurrentSlug());
|
|
154
|
-
var activeSlug = activeFamily.parent ? activeFamily.parent.slug : getCachedCurrentSlug();
|
|
155
|
-
|
|
156
|
-
for (var i = 0; i < parents.length; i++) {
|
|
150
|
+
for (var i = 0; i < getCachedProjectList().length; i++) {
|
|
157
151
|
(function (p) {
|
|
158
|
-
var family = familyOf(projects, p.slug);
|
|
159
|
-
var displayProject = aggregateFamily(p, family.worktrees);
|
|
160
|
-
var hasWorktreeUnread = family.worktrees.some(function (worktree) { return worktree.unread > 0; });
|
|
161
|
-
var hasWorktreePending = family.worktrees.some(function (worktree) { return worktree.pendingPermissions > 0; });
|
|
162
152
|
var el = document.createElement("button");
|
|
163
|
-
el.className = "mobile-project-item" + (p.slug ===
|
|
164
|
-
if ((displayProject.pendingPermissions > 0 && p.slug !== activeSlug) || hasWorktreePending) {
|
|
165
|
-
el.classList.add("has-pending-perm");
|
|
166
|
-
}
|
|
153
|
+
el.className = "mobile-project-item" + (p.slug === getCachedCurrentSlug() ? " active" : "");
|
|
167
154
|
|
|
168
155
|
var abbrev = document.createElement("span");
|
|
169
156
|
abbrev.className = "mobile-project-abbrev";
|
|
@@ -180,16 +167,16 @@ function renderSheetProjects(listEl) {
|
|
|
180
167
|
name.textContent = p.name;
|
|
181
168
|
el.appendChild(name);
|
|
182
169
|
|
|
183
|
-
if (
|
|
170
|
+
if (p.isProcessing) {
|
|
184
171
|
var dot = document.createElement("span");
|
|
185
172
|
dot.className = "mobile-project-processing";
|
|
186
173
|
el.appendChild(dot);
|
|
187
174
|
}
|
|
188
175
|
|
|
189
|
-
if (
|
|
176
|
+
if (p.unread > 0 && p.slug !== getCachedCurrentSlug()) {
|
|
190
177
|
var mBadge = document.createElement("span");
|
|
191
178
|
mBadge.className = "mobile-project-unread";
|
|
192
|
-
mBadge.textContent =
|
|
179
|
+
mBadge.textContent = p.unread > 99 ? "99+" : String(p.unread);
|
|
193
180
|
el.appendChild(mBadge);
|
|
194
181
|
}
|
|
195
182
|
|
|
@@ -199,7 +186,7 @@ function renderSheetProjects(listEl) {
|
|
|
199
186
|
});
|
|
200
187
|
|
|
201
188
|
listEl.appendChild(el);
|
|
202
|
-
})(
|
|
189
|
+
})(getCachedProjectList()[i]);
|
|
203
190
|
}
|
|
204
191
|
}
|
|
205
192
|
|
|
@@ -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) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
2
|
+
import { VENDOR_AVATARS } from './app-rendering.js';
|
|
2
3
|
|
|
3
4
|
var ctx;
|
|
4
5
|
var notes = new Map(); // id -> { data, el }
|
|
@@ -256,6 +257,15 @@ function renderNote(data) {
|
|
|
256
257
|
var header = document.createElement("div");
|
|
257
258
|
header.className = "sticky-note-header";
|
|
258
259
|
|
|
260
|
+
if (data.origin && VENDOR_AVATARS[data.origin.vendor]) {
|
|
261
|
+
var originBadge = document.createElement("img");
|
|
262
|
+
originBadge.className = "sticky-note-agent-badge";
|
|
263
|
+
originBadge.src = VENDOR_AVATARS[data.origin.vendor];
|
|
264
|
+
originBadge.alt = "";
|
|
265
|
+
originBadge.title = "Created by " + data.origin.vendor;
|
|
266
|
+
header.appendChild(originBadge);
|
|
267
|
+
}
|
|
268
|
+
|
|
259
269
|
var closeBtn = document.createElement("button");
|
|
260
270
|
closeBtn.className = "sticky-note-btn sticky-note-close";
|
|
261
271
|
closeBtn.title = "Close";
|
|
@@ -916,6 +926,17 @@ export function handleNoteUpdated(msg) {
|
|
|
916
926
|
if (archiveOpen) renderArchiveCards();
|
|
917
927
|
}
|
|
918
928
|
|
|
929
|
+
export function handleNoteWritten(msg) {
|
|
930
|
+
var entry = notes.get(msg.id);
|
|
931
|
+
if (!entry) return;
|
|
932
|
+
entry.el.classList.remove("sticky-note-attention");
|
|
933
|
+
void entry.el.offsetWidth;
|
|
934
|
+
entry.el.classList.add("sticky-note-attention");
|
|
935
|
+
setTimeout(function () {
|
|
936
|
+
entry.el.classList.remove("sticky-note-attention");
|
|
937
|
+
}, 2100);
|
|
938
|
+
}
|
|
939
|
+
|
|
919
940
|
export function handleNoteDeleted(msg) {
|
|
920
941
|
var entry = notes.get(msg.id);
|
|
921
942
|
if (!entry) return;
|