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.
@@ -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
- var textEl = document.createElement("span");
479
- textEl.textContent = text;
480
- bubble.appendChild(textEl);
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: switcherProjectName(projects, p),
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 projects = getCachedProjectList();
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 === activeSlug ? " active" : "");
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 (displayProject.isProcessing) {
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 (displayProject.unread > 0 && (p.slug !== activeSlug || hasWorktreeUnread)) {
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 = displayProject.unread > 99 ? "99+" : String(displayProject.unread);
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
- })(parents[i]);
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
- // Worktree creation lives in the title-bar branch chip, not here.
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 && (!isActive || p.hasWorktreeUnread)) {
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 ((!isActive && (p.pendingPermissions > 0 || p.hasFamilyPendingAttention || getPendingTuiAttention(p.slug) > 0)) ||
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
- export function showWorktreeModal(parentSlug, parentName) {
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 familyProject = aggregateProjectFamily(p, worktrees);
1178
- var el = createIconItem(familyProject, currentSlug);
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, el);
1186
- setupDragHandlers(el, p.slug);
1187
- container.appendChild(el);
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
- list.appendChild(createMobileProjectItem(aggregateProjectFamily(p, worktrees), currentSlug));
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 = "&#9660;";
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
- 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
- }
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 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 + '"]');
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 (displayCount > 0) {
1289
- badge.textContent = displayCount > 99 ? "99+" : String(displayCount);
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;