clay-server 5.0.2-beta.1 → 5.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.
Files changed (162) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/lib/daemon-project-access.js +7 -1
  4. package/lib/daemon.js +2 -0
  5. package/lib/dicebear-avatar.js +62 -0
  6. package/lib/knowledge-db-service.js +175 -0
  7. package/lib/knowledge-db-validation.js +0 -0
  8. package/lib/knowledge-document-index.js +49 -0
  9. package/lib/knowledge-draft-store.js +56 -0
  10. package/lib/knowledge-file-store.js +199 -0
  11. package/lib/knowledge-markdown.js +128 -0
  12. package/lib/mate-knowledge-mcp-server.js +29 -6
  13. package/lib/mate-knowledge-service.js +23 -2
  14. package/lib/mate-knowledge-sync.js +36 -21
  15. package/lib/mate-workspace-color.js +4 -0
  16. package/lib/mates.js +18 -0
  17. package/lib/project-connection.js +36 -2
  18. package/lib/project-debate-proposal.js +6 -0
  19. package/lib/project-debate-workspace.js +43 -0
  20. package/lib/project-debate.js +1 -0
  21. package/lib/project-file-path.js +2 -3
  22. package/lib/project-knowledge.js +315 -136
  23. package/lib/project-mate-creation-proposal.js +1 -0
  24. package/lib/project-request-access.js +13 -1
  25. package/lib/project-sessions.js +39 -2
  26. package/lib/project-user-knowledge.js +60 -0
  27. package/lib/project.js +59 -18
  28. package/lib/public/app.js +73 -34
  29. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  30. package/lib/public/css/clay-workspace.css +105 -0
  31. package/lib/public/css/debates-workbench.css +47 -0
  32. package/lib/public/css/filebrowser.css +10 -0
  33. package/lib/public/css/home-debate-live.css +26 -0
  34. package/lib/public/css/home-hub.css +3 -1
  35. package/lib/public/css/home-mate-model-picker.css +15 -131
  36. package/lib/public/css/home-mate-settings.css +168 -16
  37. package/lib/public/css/icon-strip.css +249 -32
  38. package/lib/public/css/mate-conversation.css +176 -0
  39. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  40. package/lib/public/css/mate-workspace-color.css +16 -0
  41. package/lib/public/css/mates.css +28 -28
  42. package/lib/public/css/messages.css +1 -114
  43. package/lib/public/css/mobile-nav.css +16 -16
  44. package/lib/public/css/overlays.css +8 -0
  45. package/lib/public/css/project-logs.css +7 -0
  46. package/lib/public/css/project-mate-creation.css +105 -0
  47. package/lib/public/css/right-workbench.css +3 -3
  48. package/lib/public/css/session-folders.css +80 -21
  49. package/lib/public/css/sidebar.css +183 -107
  50. package/lib/public/css/title-bar.css +151 -0
  51. package/lib/public/css/worker-delegation.css +47 -0
  52. package/lib/public/css/worker-proposal.css +72 -150
  53. package/lib/public/css/you.css +74 -0
  54. package/lib/public/index.html +33 -101
  55. package/lib/public/modules/account-island.js +230 -0
  56. package/lib/public/modules/app-cursors.js +19 -43
  57. package/lib/public/modules/app-debate-ui.js +4 -3
  58. package/lib/public/modules/app-header.js +106 -0
  59. package/lib/public/modules/app-message-router.js +12 -3
  60. package/lib/public/modules/app-messages.js +34 -9
  61. package/lib/public/modules/app-notifications.js +4 -7
  62. package/lib/public/modules/app-projects.js +5 -38
  63. package/lib/public/modules/app-rendering.js +12 -13
  64. package/lib/public/modules/avatar.js +25 -9
  65. package/lib/public/modules/chat-render-runtime.js +16 -4
  66. package/lib/public/modules/command-palette.js +2 -15
  67. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  68. package/lib/public/modules/debate-controls.js +299 -0
  69. package/lib/public/modules/debate.js +2 -0
  70. package/lib/public/modules/debates-workbench.js +339 -0
  71. package/lib/public/modules/default-ai.js +81 -65
  72. package/lib/public/modules/filebrowser.js +28 -8
  73. package/lib/public/modules/home-debate-controls.js +18 -289
  74. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  75. package/lib/public/modules/home-mate-color-picker.js +66 -0
  76. package/lib/public/modules/home-mate-creation.js +1 -1
  77. package/lib/public/modules/home-mate-instructions.js +90 -0
  78. package/lib/public/modules/home-mate-model-picker.js +80 -271
  79. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  80. package/lib/public/modules/home-mate-settings.js +118 -125
  81. package/lib/public/modules/home-shell.js +3 -0
  82. package/lib/public/modules/home-surface-boot.js +1 -9
  83. package/lib/public/modules/html-preview.js +19 -0
  84. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  85. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  86. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  87. package/lib/public/modules/knowledge-history-state.js +55 -0
  88. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  89. package/lib/public/modules/knowledge-preview-links.js +70 -0
  90. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  91. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  92. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  93. package/lib/public/modules/mate-knowledge.js +11 -3
  94. package/lib/public/modules/mate-management.js +54 -10
  95. package/lib/public/modules/mate-sidebar.js +11 -1
  96. package/lib/public/modules/mate-workspace-color.js +33 -0
  97. package/lib/public/modules/mention.js +5 -13
  98. package/lib/public/modules/profile.js +51 -18
  99. package/lib/public/modules/project-chat-identity.js +79 -0
  100. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  101. package/lib/public/modules/project-debate-workspace.js +185 -0
  102. package/lib/public/modules/project-header-identity.js +127 -0
  103. package/lib/public/modules/project-logs-render.js +9 -4
  104. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  105. package/lib/public/modules/project-mate-navigation.js +238 -0
  106. package/lib/public/modules/project-session-navigation.js +29 -0
  107. package/lib/public/modules/project-switcher.js +6 -8
  108. package/lib/public/modules/projects-hub-view.js +5 -6
  109. package/lib/public/modules/projects-hub.js +2 -6
  110. package/lib/public/modules/qrcode.js +27 -8
  111. package/lib/public/modules/search-clay-chat.js +8 -11
  112. package/lib/public/modules/session-activity-tooltip.js +12 -0
  113. package/lib/public/modules/session-create-form.js +86 -14
  114. package/lib/public/modules/session-create.js +50 -7
  115. package/lib/public/modules/session-list-visibility.js +41 -0
  116. package/lib/public/modules/session-presence-ui.js +4 -1
  117. package/lib/public/modules/session-title-marquee.js +51 -0
  118. package/lib/public/modules/sidebar-mates.js +3 -50
  119. package/lib/public/modules/sidebar-mobile.js +63 -60
  120. package/lib/public/modules/sidebar-projects.js +84 -97
  121. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  122. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  123. package/lib/public/modules/sidebar-sessions.js +49 -50
  124. package/lib/public/modules/sidebar.js +11 -13
  125. package/lib/public/modules/thinking-lifecycle.js +7 -6
  126. package/lib/public/modules/tool-palette-order.js +4 -3
  127. package/lib/public/modules/tool-palette.js +13 -3
  128. package/lib/public/modules/tools.js +22 -22
  129. package/lib/public/modules/worker-proposal.js +42 -8
  130. package/lib/public/modules/worker-runtime-picker.js +115 -0
  131. package/lib/public/modules/you-activity.js +121 -0
  132. package/lib/public/modules/you.js +251 -0
  133. package/lib/public/style.css +16 -8
  134. package/lib/public/vendor/codemirror/LICENSE +21 -0
  135. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  136. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  137. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  138. package/lib/public/vendor/codemirror/markdown.js +886 -0
  139. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  140. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  141. package/lib/sdk-bridge.js +15 -3
  142. package/lib/server-admin.js +11 -0
  143. package/lib/server-home-chat-events.js +1 -1
  144. package/lib/server-home-chat.js +25 -3
  145. package/lib/server-home-debate-planning.js +4 -1
  146. package/lib/server-home-debates.js +2 -0
  147. package/lib/server-home-mate-creation.js +4 -1
  148. package/lib/server-mate-instructions.js +44 -0
  149. package/lib/server-mates.js +12 -3
  150. package/lib/server-palette.js +1 -1
  151. package/lib/server-settings.js +65 -1
  152. package/lib/server-user-knowledge.js +119 -0
  153. package/lib/server.js +21 -1
  154. package/lib/session-visibility.js +7 -2
  155. package/lib/sessions.js +32 -6
  156. package/lib/user-knowledge-service.js +167 -0
  157. package/lib/users-permissions.js +7 -0
  158. package/lib/users-project-mate-filter-preferences.js +39 -0
  159. package/lib/users.js +6 -0
  160. package/lib/workspace-query-access.js +4 -1
  161. package/lib/ws-schema.js +50 -0
  162. package/package.json +10 -1
@@ -9,13 +9,16 @@ import { parseEmojis } from './markdown.js';
9
9
  import { store } from './store.js';
10
10
  import { getWs } from './ws-ref.js';
11
11
  import { closeSidebar } from './sidebar.js';
12
- import { showIconTooltip, hideIconTooltip, closeUserCtxMenu, getCurrentDmUserId } from './sidebar-mates.js';
12
+ import { showIconTooltip, hideIconTooltip, bindIconTooltip, closeUserCtxMenu, getCurrentDmUserId } from './sidebar-mates.js';
13
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
16
  import { isExternalWorktree, externalWorktreeTooltip, appendExternalWorktreeBadge } from './worktree-location.js';
17
17
  import { groupProjects } from './project-grouping.js';
18
18
  import { showProjectAccessPopover } from './project-access.js';
19
+ import { renderProjectMates, mateForWorkspace } from './project-mate-navigation.js';
20
+ import { openProjectCreateChoice } from './project-mate-creation-workspace.js';
21
+ import { openHomeMateSettings } from './home-mate-settings.js';
19
22
 
20
23
  // --- Project state ---
21
24
  var cachedProjectList = [];
@@ -23,6 +26,7 @@ var cachedCurrentSlug = null;
23
26
 
24
27
  // --- Project context menu ---
25
28
  var projectCtxMenu = null;
29
+ var projectCtxAnchor = null;
26
30
 
27
31
  // --- Emoji picker ---
28
32
  var emojiPickerEl = null;
@@ -250,14 +254,15 @@ export function initSidebarProjects() {
250
254
  var addBtn = document.getElementById("icon-strip-add");
251
255
  if (addBtn) {
252
256
  addBtn.addEventListener("click", function () {
253
- if (openAddProjectModal) {
254
- openAddProjectModal();
255
- } else {
256
- var modal = document.getElementById("add-project-modal");
257
- if (modal) modal.classList.remove("hidden");
258
- }
257
+ openProjectCreateChoice(addBtn, function () {
258
+ if (openAddProjectModal) openAddProjectModal();
259
+ else {
260
+ var modal = document.getElementById("add-project-modal");
261
+ if (modal) modal.classList.remove("hidden");
262
+ }
263
+ });
259
264
  });
260
- addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add project"); });
265
+ addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add Mate or project"); });
261
266
  addBtn.addEventListener("mouseleave", hideIconTooltip);
262
267
  }
263
268
 
@@ -273,7 +278,7 @@ export function initSidebarProjects() {
273
278
 
274
279
  // Tooltip + click for home icon
275
280
  var homeIcon = document.querySelector(".icon-strip-home");
276
- if (homeIcon) {
281
+ if (homeIcon && !homeIcon.classList.contains("icon-strip-brand")) {
277
282
  homeIcon.addEventListener("mouseenter", function () { showIconTooltip(homeIcon, "Clay Studio"); });
278
283
  homeIcon.addEventListener("mouseleave", hideIconTooltip);
279
284
  homeIcon.addEventListener("click", function (e) {
@@ -288,6 +293,11 @@ export function initSidebarProjects() {
288
293
  if (dropdownBtn) {
289
294
  dropdownBtn.addEventListener("click", function (e) {
290
295
  e.stopPropagation();
296
+ var currentMate = mateForWorkspace(store.snap());
297
+ if (currentMate) {
298
+ openHomeMateSettings(currentMate.id, dropdownBtn, { section: "general" });
299
+ return;
300
+ }
291
301
  var current = null;
292
302
  for (var i = 0; i < cachedProjectList.length; i++) {
293
303
  if (cachedProjectList[i].slug === cachedCurrentSlug) {
@@ -339,82 +349,21 @@ export { getProjectAbbrev };
339
349
 
340
350
  // --- Project context menu ---
341
351
 
342
- export function closeProjectCtxMenu() {
352
+ export function closeProjectCtxMenu(options) {
343
353
  if (projectCtxMenu) {
344
354
  projectCtxMenu.remove();
345
355
  projectCtxMenu = null;
346
356
  }
357
+ if (options && options.restoreFocus && projectCtxAnchor && projectCtxAnchor.isConnected) projectCtxAnchor.focus();
358
+ projectCtxAnchor = null;
347
359
  }
348
360
 
349
- function showIconCtxMenu(anchorEl, slug, name, options) {
350
- closeProjectCtxMenu();
351
- if (closeUserCtxMenu) closeUserCtxMenu();
352
- closeEmojiPicker();
353
-
354
- var menu = document.createElement("div");
355
- menu.className = "project-ctx-menu";
356
-
357
- var isWorktree = slug.indexOf("--") !== -1;
358
-
359
- var iconItem = document.createElement("button");
360
- iconItem.className = "project-ctx-item";
361
- iconItem.innerHTML = iconHtml("smile") + " <span>Set Icon</span>";
362
- iconItem.addEventListener("click", function (e) {
363
- e.stopPropagation();
364
- closeProjectCtxMenu();
365
- showEmojiPicker(slug, anchorEl);
366
- });
367
- menu.appendChild(iconItem);
368
-
369
- if (isWorktree) {
370
- if (!options || options.canRemoveWorktree !== false) {
371
- var removeWtItem = document.createElement("button");
372
- removeWtItem.className = "project-ctx-item project-ctx-delete";
373
- removeWtItem.innerHTML = iconHtml("trash-2") + " <span>Remove Worktree</span>";
374
- removeWtItem.addEventListener("click", function (e) {
375
- e.stopPropagation();
376
- closeProjectCtxMenu();
377
- if (getWs() && store.get('connected')) {
378
- getWs().send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name || slug }));
379
- }
380
- });
381
- menu.appendChild(removeWtItem);
382
- }
383
- } else {
384
- var wtItem = document.createElement("button");
385
- wtItem.className = "project-ctx-item";
386
- wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
387
- wtItem.addEventListener("click", function (e) {
388
- e.stopPropagation();
389
- closeProjectCtxMenu();
390
- showWorktreeModal(slug, name || slug);
391
- });
392
- menu.appendChild(wtItem);
393
- }
394
-
395
- document.body.appendChild(menu);
396
- projectCtxMenu = menu;
397
- refreshIcons();
398
-
399
- requestAnimationFrame(function () {
400
- var rect = anchorEl.getBoundingClientRect();
401
- menu.style.position = "fixed";
402
- menu.style.left = (rect.right + 6) + "px";
403
- menu.style.top = rect.top + "px";
404
- var menuRect = menu.getBoundingClientRect();
405
- if (menuRect.right > window.innerWidth - 8) {
406
- menu.style.left = (rect.left - menuRect.width - 6) + "px";
407
- }
408
- if (menuRect.bottom > window.innerHeight - 8) {
409
- menu.style.top = (window.innerHeight - menuRect.height - 8) + "px";
410
- }
411
- });
412
- }
413
-
414
- function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
361
+ export function showProjectCtxMenu(anchorEl, slug, name, icon, position, options) {
362
+ options = options || {};
415
363
  closeProjectCtxMenu();
416
364
  if (closeUserCtxMenu) closeUserCtxMenu();
417
365
  closeEmojiPicker();
366
+ projectCtxAnchor = anchorEl;
418
367
  var projects = getCachedProjects();
419
368
  var targetProject = null;
420
369
  for (var projectIndex = 0; projectIndex < projects.length; projectIndex++) {
@@ -423,10 +372,12 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
423
372
 
424
373
  var menu = document.createElement("div");
425
374
  menu.className = "project-ctx-menu";
375
+ menu.setAttribute("role", "menu");
426
376
 
427
377
  // --- Set Icon ---
428
378
  var iconItem = document.createElement("button");
429
379
  iconItem.className = "project-ctx-item";
380
+ iconItem.setAttribute("role", "menuitem");
430
381
  iconItem.innerHTML = iconHtml("smile") + " <span>Set Icon</span>";
431
382
  iconItem.addEventListener("click", function (e) {
432
383
  e.stopPropagation();
@@ -439,6 +390,7 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
439
390
  if (!store.get('permissions') || store.get('permissions').projectSettings !== false) {
440
391
  var settingsItem = document.createElement("button");
441
392
  settingsItem.className = "project-ctx-item";
393
+ settingsItem.setAttribute("role", "menuitem");
442
394
  settingsItem.innerHTML = iconHtml("settings") + " <span>Project Settings</span>";
443
395
  settingsItem.addEventListener("click", function (e) {
444
396
  e.stopPropagation();
@@ -461,11 +413,12 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
461
413
  // --- Share ---
462
414
  var shareItem = document.createElement("button");
463
415
  shareItem.className = "project-ctx-item";
416
+ shareItem.setAttribute("role", "menuitem");
464
417
  shareItem.innerHTML = iconHtml("share") + " <span>Share</span>";
465
418
  shareItem.addEventListener("click", function (e) {
466
419
  e.stopPropagation();
467
420
  closeProjectCtxMenu();
468
- triggerShare();
421
+ triggerShare(options.shareTargetSlug ? { projectSlug: slug, title: name || slug } : undefined);
469
422
  });
470
423
  menu.appendChild(shareItem);
471
424
 
@@ -476,6 +429,7 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
476
429
  if (isProjectOwner || isAdmin) {
477
430
  var accessItem = document.createElement("button");
478
431
  accessItem.className = "project-ctx-item";
432
+ accessItem.setAttribute("role", "menuitem");
479
433
  accessItem.innerHTML = iconHtml("users") + " <span>Manage Access</span>";
480
434
  accessItem.addEventListener("click", function (e) {
481
435
  e.stopPropagation();
@@ -486,14 +440,20 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
486
440
  }
487
441
  }
488
442
 
489
- var sep2 = document.createElement("div");
490
- sep2.className = "project-ctx-separator";
491
- menu.appendChild(sep2);
443
+ var isWorktree = slug.indexOf("--") !== -1;
444
+ var canRemoveTarget = !isWorktree || options.canRemoveWorktree !== false;
445
+ var canDeleteTarget = canRemoveTarget && (!store.get('permissions') || store.get('permissions').deleteProject !== false);
446
+ if (!isWorktree || canDeleteTarget) {
447
+ var sep2 = document.createElement("div");
448
+ sep2.className = "project-ctx-separator";
449
+ menu.appendChild(sep2);
450
+ }
492
451
 
493
452
  // --- Add Worktree ---
494
453
  if (slug.indexOf("--") === -1) {
495
454
  var wtItem = document.createElement("button");
496
455
  wtItem.className = "project-ctx-item";
456
+ wtItem.setAttribute("role", "menuitem");
497
457
  wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
498
458
  wtItem.addEventListener("click", function (e) {
499
459
  e.stopPropagation();
@@ -503,14 +463,17 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
503
463
  menu.appendChild(wtItem);
504
464
  }
505
465
 
506
- if (!store.get('permissions') || store.get('permissions').deleteProject !== false) {
507
- var sep3 = document.createElement("div");
508
- sep3.className = "project-ctx-separator";
509
- menu.appendChild(sep3);
466
+ if (canDeleteTarget) {
467
+ if (!isWorktree) {
468
+ var sep3 = document.createElement("div");
469
+ sep3.className = "project-ctx-separator";
470
+ menu.appendChild(sep3);
471
+ }
510
472
 
511
473
  var deleteItem = document.createElement("button");
512
474
  deleteItem.className = "project-ctx-item project-ctx-delete";
513
- deleteItem.innerHTML = iconHtml("trash-2") + " <span>Remove Project</span>";
475
+ deleteItem.setAttribute("role", "menuitem");
476
+ deleteItem.innerHTML = iconHtml("trash-2") + " <span>" + (isWorktree ? "Remove Worktree" : "Remove Project") + "</span>";
514
477
  deleteItem.addEventListener("click", function (e) {
515
478
  e.stopPropagation();
516
479
  closeProjectCtxMenu();
@@ -525,6 +488,28 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
525
488
  projectCtxMenu = menu;
526
489
  refreshIcons();
527
490
 
491
+ menu.addEventListener("keydown", function (event) {
492
+ var items = Array.from(menu.querySelectorAll('.project-ctx-item:not([disabled])'));
493
+ var index = items.indexOf(document.activeElement);
494
+ if (event.key === "Escape") {
495
+ event.preventDefault();
496
+ closeProjectCtxMenu({ restoreFocus: true });
497
+ return;
498
+ }
499
+ if (event.key === "Tab") {
500
+ closeProjectCtxMenu();
501
+ return;
502
+ }
503
+ if (event.key === "Home" || event.key === "End" || event.key === "ArrowDown" || event.key === "ArrowUp") {
504
+ event.preventDefault();
505
+ if (event.key === "Home") index = 0;
506
+ else if (event.key === "End") index = items.length - 1;
507
+ else if (event.key === "ArrowDown") index = (index + 1 + items.length) % items.length;
508
+ else index = (index - 1 + items.length) % items.length;
509
+ if (items[index]) items[index].focus();
510
+ }
511
+ });
512
+
528
513
  requestAnimationFrame(function () {
529
514
  var rect = anchorEl.getBoundingClientRect();
530
515
  menu.style.position = "fixed";
@@ -542,6 +527,8 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
542
527
  if (menuRect.bottom > window.innerHeight - 8) {
543
528
  menu.style.top = (window.innerHeight - menuRect.height - 8) + "px";
544
529
  }
530
+ var firstItem = menu.querySelector('.project-ctx-item:not([disabled])');
531
+ if (firstItem) firstItem.focus();
545
532
  });
546
533
  }
547
534
 
@@ -797,7 +784,7 @@ function setupDragHandlers(el, slug) {
797
784
  var insertBefore = e.clientY < midY;
798
785
 
799
786
  var container = document.getElementById("icon-strip-projects");
800
- var items = container.querySelectorAll(".icon-strip-item");
787
+ var items = container.querySelectorAll(".icon-strip-item[data-slug]");
801
788
  var slugs = [];
802
789
  for (var i = 0; i < items.length; i++) {
803
790
  if (items[i].dataset.slug !== draggedSlug) {
@@ -873,10 +860,7 @@ function createIconItem(p, currentSlug) {
873
860
  el.classList.add("has-pending-perm");
874
861
  }
875
862
 
876
- (function (name, elem) {
877
- elem.addEventListener("mouseenter", function () { showIconTooltip(elem, name); });
878
- elem.addEventListener("mouseleave", hideIconTooltip);
879
- })(p.name, el);
863
+ bindIconTooltip(el, { text: p.name, kind: "project" });
880
864
 
881
865
  (function (slug) {
882
866
  el.addEventListener("click", function (e) {
@@ -1037,6 +1021,7 @@ export function renderIconStrip(projects, currentSlug) {
1037
1021
 
1038
1022
  var container = document.getElementById("icon-strip-projects");
1039
1023
  if (!container) return;
1024
+ var focusedMateId = container.contains(document.activeElement) ? document.activeElement.dataset.mateId : null;
1040
1025
  container.innerHTML = "";
1041
1026
 
1042
1027
  var currentDmUserId = getCurrentDmUserId ? getCurrentDmUserId() : null;
@@ -1053,7 +1038,7 @@ export function renderIconStrip(projects, currentSlug) {
1053
1038
  elem.addEventListener("contextmenu", function (e) {
1054
1039
  e.preventDefault();
1055
1040
  e.stopPropagation();
1056
- showIconCtxMenu(elem, slug, name);
1041
+ showProjectCtxMenu(elem, slug, name, null, null, { shareTargetSlug: true });
1057
1042
  });
1058
1043
  })(p.slug, p.name || p.slug, el);
1059
1044
  setupDragHandlers(el, p.slug);
@@ -1079,7 +1064,7 @@ export function renderIconStrip(projects, currentSlug) {
1079
1064
  elem.addEventListener("contextmenu", function (e) {
1080
1065
  e.preventDefault();
1081
1066
  e.stopPropagation();
1082
- showIconCtxMenu(elem, slug, name);
1067
+ showProjectCtxMenu(elem, slug, name, null, null, { shareTargetSlug: true });
1083
1068
  });
1084
1069
  })(p.slug, p.name || p.slug, header);
1085
1070
  setupDragHandlers(header, p.slug);
@@ -1136,10 +1121,7 @@ export function renderIconStrip(projects, currentSlug) {
1136
1121
  if (isExternal) appendExternalWorktreeBadge(wtEl, "worktree-external-badge");
1137
1122
 
1138
1123
  var tooltipText = isExternal ? externalWorktreeTooltip(wt.name) : wt.name;
1139
- (function (text, elem) {
1140
- elem.addEventListener("mouseenter", function () { showIconTooltip(elem, text); });
1141
- elem.addEventListener("mouseleave", hideIconTooltip);
1142
- })(tooltipText, wtEl);
1124
+ bindIconTooltip(wtEl, { text: tooltipText, kind: "project" });
1143
1125
 
1144
1126
  (function (slug) {
1145
1127
  wtEl.addEventListener("click", function (e) {
@@ -1152,7 +1134,7 @@ export function renderIconStrip(projects, currentSlug) {
1152
1134
  elem.addEventListener("contextmenu", function (e) {
1153
1135
  e.preventDefault();
1154
1136
  e.stopPropagation();
1155
- showIconCtxMenu(elem, slug, name, { canRemoveWorktree: !external });
1137
+ showProjectCtxMenu(elem, slug, name, null, null, { shareTargetSlug: true, canRemoveWorktree: !external });
1156
1138
  });
1157
1139
  })(wt.slug, wt.name, wtEl, isExternal);
1158
1140
 
@@ -1202,6 +1184,11 @@ export function renderIconStrip(projects, currentSlug) {
1202
1184
  }
1203
1185
 
1204
1186
  renderProjectList(projects, currentSlug);
1187
+ renderProjectMates();
1188
+ if (focusedMateId) {
1189
+ var mateButton = container.querySelector('[data-mate-id="' + CSS.escape(focusedMateId) + '"]');
1190
+ if (mateButton) mateButton.focus({ preventScroll: true });
1191
+ }
1205
1192
 
1206
1193
  var iconNodes = [container];
1207
1194
  var mobileProjectList = document.getElementById("project-list");
@@ -17,7 +17,7 @@ import {
17
17
  } from './session-folder-layout.js';
18
18
  import { openFolderNameDialog, openFolderPicker, closeFolderModal } from './session-folder-dialogs.js';
19
19
  import { openFolderDelete } from './session-folder-delete.js';
20
- import { openSessionCreate } from './session-create.js';
20
+ import { beginSessionCreate } from './session-create.js';
21
21
  import { renderNewSessionButton, renderCreateRow } from './session-create-form.js';
22
22
  import { restoreFolderCollapse, toggleFolderCollapse } from './session-folder-collapse.js';
23
23
  import { groupUnitsByDate, groupRootSessionIds } from './session-date-groups.js';
@@ -286,7 +286,7 @@ function folderMenuEntries(section, ctx) {
286
286
  var at = folders.map(function (f) { return f.id; }).indexOf(section.id);
287
287
  var folder = { id: section.id, label: section.label };
288
288
  return [
289
- { label: "New session here", icon: "plus", run: function () { openSessionCreate(section.id); } },
289
+ { label: "New session here", icon: "plus", run: function () { beginSessionCreate(section.id); } },
290
290
  { label: "Rename", icon: "pencil", run: function () { renameFolder(folder); } },
291
291
  { label: "Move up", icon: "arrow-up", disabled: at <= 0, run: function () { shiftFolder(section.id, -1); } },
292
292
  { label: "Move down", icon: "arrow-down", disabled: at === -1 || at >= folders.length - 1, run: function () { shiftFolder(section.id, 1); } },
@@ -2,13 +2,150 @@
2
2
 
3
3
  import { iconHtml } from './icons.js';
4
4
  import { store } from './store.js';
5
+ import { getWs } from './ws-ref.js';
5
6
  import { buildSessionHierarchy } from './session-hierarchy.js';
7
+ import { splitGroupRoles } from './split-group-helpers.js';
6
8
 
7
9
  var expansionBySurface = {
8
10
  desktop: new Map(),
9
11
  mobile: new Map(),
10
12
  };
11
13
 
14
+ var workerHistoryMenu = null;
15
+ var workerHistoryOpener = null;
16
+ var workerHistoryKeyHandler = null;
17
+ var workerHistoryOutsideHandler = null;
18
+
19
+ export function closeWorkerHistoryMenu(restoreFocus) {
20
+ if (workerHistoryMenu) {
21
+ workerHistoryMenu.remove();
22
+ workerHistoryMenu = null;
23
+ }
24
+ if (typeof document !== "undefined" && workerHistoryKeyHandler) document.removeEventListener("keydown", workerHistoryKeyHandler, true);
25
+ if (typeof document !== "undefined" && workerHistoryOutsideHandler) document.removeEventListener("click", workerHistoryOutsideHandler, true);
26
+ workerHistoryKeyHandler = null;
27
+ workerHistoryOutsideHandler = null;
28
+ var opener = workerHistoryOpener;
29
+ workerHistoryOpener = null;
30
+ if (restoreFocus !== false && opener && typeof document !== "undefined" && document.contains(opener)) opener.focus();
31
+ }
32
+
33
+ function workerIsCurrent(worker) {
34
+ var groups = store.get('splitGroups') || [];
35
+ for (var i = 0; i < groups.length; i++) {
36
+ var roles = splitGroupRoles(groups[i]);
37
+ if (roles && roles.workerIds.indexOf(worker.id) !== -1) return true;
38
+ }
39
+ return false;
40
+ }
41
+
42
+ function workerLabel(worker) {
43
+ var generation = worker && worker.workerGeneration ? "Generation " + worker.workerGeneration : "Worker";
44
+ return generation + (workerIsCurrent(worker) ? " · Current" : " · History");
45
+ }
46
+
47
+ function openWorkerHistoryMenu(button, driver, workers, options) {
48
+ var opts = options || {};
49
+ if (opts.validate && !opts.validate(driver, workers)) return;
50
+ closeWorkerHistoryMenu();
51
+ var menu = document.createElement("div");
52
+ menu.className = "session-worker-history-menu";
53
+ menu.setAttribute("role", "menu");
54
+ menu.setAttribute("aria-label", "Worker history for " + (driver.title || "this session"));
55
+ var heading = document.createElement("div");
56
+ heading.className = "session-worker-history-heading";
57
+ heading.textContent = "Worker history";
58
+ menu.appendChild(heading);
59
+ for (var i = 0; i < workers.length; i++) {
60
+ var worker = workers[i];
61
+ var item = document.createElement("button");
62
+ item.type = "button";
63
+ item.className = "session-worker-history-item" + (workerIsCurrent(worker) ? " current" : "");
64
+ item.setAttribute("role", "menuitem");
65
+ item.setAttribute("aria-label", workerLabel(worker));
66
+ var label = document.createElement("span");
67
+ label.className = "session-worker-history-label";
68
+ label.textContent = workerLabel(worker);
69
+ var title = document.createElement("span");
70
+ title.className = "session-worker-history-title";
71
+ title.textContent = worker.title || "Untitled Worker";
72
+ item.appendChild(label);
73
+ item.appendChild(title);
74
+ item.addEventListener("click", (function (id, historyWorker) {
75
+ return function (event) {
76
+ event.preventDefault();
77
+ event.stopPropagation();
78
+ var ws = getWs();
79
+ var valid = ws && ws.readyState === 1 && store.get('connected') && (!opts.validate || opts.validate(driver, workers));
80
+ var navigated = false;
81
+ if (valid && opts.navigate) navigated = opts.navigate(historyWorker) !== false;
82
+ else if (valid) {
83
+ ws.send(JSON.stringify({ type: "switch_session", id: id }));
84
+ navigated = true;
85
+ }
86
+ if (navigated) closeWorkerHistoryMenu(false);
87
+ else closeWorkerHistoryMenu();
88
+ };
89
+ })(worker.id, worker));
90
+ menu.appendChild(item);
91
+ }
92
+ document.body.appendChild(menu);
93
+ var rect = button.getBoundingClientRect();
94
+ var menuRect = menu.getBoundingClientRect();
95
+ var left = Math.max(8, Math.min(rect.right - menuRect.width, window.innerWidth - menuRect.width - 8));
96
+ var top = rect.bottom + 4;
97
+ if (top + menuRect.height > window.innerHeight - 8) top = Math.max(8, rect.top - menuRect.height - 4);
98
+ menu.style.left = left + "px";
99
+ menu.style.top = top + "px";
100
+ workerHistoryMenu = menu;
101
+ workerHistoryOpener = button;
102
+ workerHistoryKeyHandler = function (event) {
103
+ var items = menu.querySelectorAll(".session-worker-history-item");
104
+ var currentIndex = -1;
105
+ for (var ii = 0; ii < items.length; ii++) if (items[ii] === document.activeElement) currentIndex = ii;
106
+ if (event.key === "Escape") {
107
+ event.preventDefault();
108
+ closeWorkerHistoryMenu();
109
+ } else if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Home" || event.key === "End") {
110
+ event.preventDefault();
111
+ if (!items.length) return;
112
+ var nextIndex = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : currentIndex + (event.key === "ArrowDown" ? 1 : -1);
113
+ if (nextIndex < 0) nextIndex = items.length - 1;
114
+ if (nextIndex >= items.length) nextIndex = 0;
115
+ items[nextIndex].focus();
116
+ }
117
+ };
118
+ workerHistoryOutsideHandler = function (event) {
119
+ if (!menu.contains(event.target) && event.target !== button) closeWorkerHistoryMenu();
120
+ };
121
+ document.addEventListener("keydown", workerHistoryKeyHandler, true);
122
+ document.addEventListener("click", workerHistoryOutsideHandler, true);
123
+ var firstItem = menu.querySelector(".session-worker-history-item");
124
+ if (firstItem) firstItem.focus();
125
+ }
126
+
127
+ export function createWorkerHistoryControl(driver, workers, options) {
128
+ if (!workers || !workers.length) return null;
129
+ var opts = options || {};
130
+ var button = document.createElement("button");
131
+ button.type = "button";
132
+ button.className = "session-worker-history-btn" + (opts.className ? " " + opts.className : "");
133
+ button.setAttribute("aria-label", opts.label || "Show Worker history");
134
+ button.setAttribute("aria-haspopup", "menu");
135
+ button.innerHTML = iconHtml("history");
136
+ button.addEventListener("click", function (event) {
137
+ event.preventDefault();
138
+ event.stopPropagation();
139
+ if (workerHistoryMenu) closeWorkerHistoryMenu();
140
+ else openWorkerHistoryMenu(button, driver, workers, opts);
141
+ });
142
+ return button;
143
+ }
144
+
145
+ store.subscribe(function (state, previous) {
146
+ if (state.activeSessionId !== previous.activeSessionId || state.currentSlug !== previous.currentSlug || state.activeProjectMateId !== previous.activeProjectMateId || state.splitPanes !== previous.splitPanes) closeWorkerHistoryMenu(false);
147
+ });
148
+
12
149
  export function prepareSidebarHierarchy(sessions) {
13
150
  var hierarchy = buildSessionHierarchy(sessions);
14
151
  var byDriver = new Map();
@@ -32,15 +169,8 @@ export function currentWorkerIds() {
32
169
  var result = new Set();
33
170
  var groups = store.get('splitGroups') || [];
34
171
  for (var i = 0; i < groups.length; i++) {
35
- var pair = groups[i].pair;
36
- if (!pair) continue;
37
- if (Array.isArray(pair.workerIds)) {
38
- for (var j = 0; j < pair.workerIds.length; j++) {
39
- if (Number.isInteger(pair.workerIds[j])) result.add(pair.workerIds[j]);
40
- }
41
- } else if (Number.isInteger(pair.workerId)) {
42
- result.add(pair.workerId);
43
- }
172
+ var roles = splitGroupRoles(groups[i]);
173
+ if (roles) for (var j = 0; j < roles.workerIds.length; j++) result.add(roles.workerIds[j]);
44
174
  }
45
175
  return result;
46
176
  }
@@ -90,46 +220,16 @@ function visibleWorker(worker, driver, matchIds) {
90
220
  export function renderDesktopDriverHierarchy(root, renderSession, rerender, matchIds) {
91
221
  var wrapper = document.createElement("div");
92
222
  wrapper.className = "session-driver-hierarchy";
93
- var key = "driver:" + root.driver.id;
94
- var isExpanded = expanded("desktop", key);
95
- var childrenId = "session-workers-" + root.driver.id;
96
223
  var header = document.createElement("div");
97
224
  header.className = "session-driver-header";
98
- var row = renderSession(root.driver);
225
+ var selectedSessionId = store.get('activeSessionId');
226
+ var anyActive = !!root.driver.active || root.workers.some(function (worker) { return worker.id === selectedSessionId; });
227
+ var anyProcessing = root.workers.some(function (worker) { return worker.isProcessing; });
228
+ var workerUnread = root.workers.reduce(function (total, worker) { return total + (worker.unread || 0); }, 0);
229
+ var row = renderSession(root.driver, { promotedActive: anyActive, promotedProcessing: anyProcessing, promotedUnread: workerUnread });
99
230
  row.classList.add("session-driver-item");
100
- var control = document.createElement("button");
101
- control.type = "button";
102
- control.className = "session-driver-toggle";
103
- control.setAttribute("aria-expanded", String(isExpanded));
104
- control.setAttribute("aria-controls", childrenId);
105
- control.setAttribute("aria-label", (isExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
106
- control.innerHTML = iconHtml("chevron-right");
107
- var children = document.createElement("div");
108
- children.id = childrenId;
109
- children.className = "session-worker-children";
110
- children.setAttribute("role", "group");
111
- children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
112
- children.hidden = !isExpanded;
113
- control.addEventListener("click", function (event) {
114
- event.preventDefault();
115
- event.stopPropagation();
116
- var nextExpanded = !isExpanded;
117
- expansionBySurface.desktop.set(expansionKey(key), nextExpanded);
118
- control.setAttribute("aria-expanded", String(nextExpanded));
119
- control.setAttribute("aria-label", (nextExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
120
- children.hidden = !nextExpanded;
121
- isExpanded = nextExpanded;
122
- });
123
- header.appendChild(control);
124
231
  header.appendChild(row);
125
232
  wrapper.appendChild(header);
126
-
127
- var current = currentWorkerIds();
128
- for (var i = 0; i < root.workers.length; i++) {
129
- if (!visibleWorker(root.workers[i], root.driver, matchIds)) continue;
130
- children.appendChild(renderSession(root.workers[i], { worker: true, current: current.has(root.workers[i].id) }));
131
- }
132
- wrapper.appendChild(children);
133
233
  return wrapper;
134
234
  }
135
235
 
@@ -164,35 +264,14 @@ export function renderDesktopOrphanHierarchy(workers, renderSession, rerender, m
164
264
  export function renderMobileDriverHierarchy(root, renderSession, rerender, matchIds) {
165
265
  var wrapper = document.createElement("div");
166
266
  wrapper.className = "mobile-driver-hierarchy";
167
- var key = "driver:" + root.driver.id;
168
- var isExpanded = expanded("mobile", key);
169
- var childrenId = "mobile-session-workers-" + root.driver.id;
170
267
  var header = document.createElement("div");
171
268
  header.className = "mobile-driver-header";
172
- var control = document.createElement("button");
173
- control.type = "button";
174
- control.className = "mobile-driver-toggle";
175
- control.setAttribute("aria-expanded", String(isExpanded));
176
- control.setAttribute("aria-controls", childrenId);
177
- control.setAttribute("aria-label", (isExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
178
- control.innerHTML = iconHtml("chevron-right");
179
- control.addEventListener("click", function () { toggle("mobile", key, isExpanded, rerender); });
180
- header.appendChild(control);
181
- header.appendChild(renderSession(root.driver));
269
+ var selectedSessionId = store.get('activeSessionId');
270
+ var anyActive = !!root.driver.active || root.workers.some(function (worker) { return worker.id === selectedSessionId; });
271
+ var anyProcessing = root.workers.some(function (worker) { return worker.isProcessing; });
272
+ var workerUnread = root.workers.reduce(function (total, worker) { return total + (worker.unread || 0); }, 0);
273
+ header.appendChild(renderSession(root.driver, { promotedActive: anyActive, promotedProcessing: anyProcessing, promotedUnread: workerUnread }));
182
274
  wrapper.appendChild(header);
183
-
184
- var children = document.createElement("div");
185
- children.id = childrenId;
186
- children.className = "mobile-worker-children";
187
- children.setAttribute("role", "group");
188
- children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
189
- children.hidden = !isExpanded;
190
- var current = currentWorkerIds();
191
- for (var i = 0; i < root.workers.length; i++) {
192
- if (!visibleWorker(root.workers[i], root.driver, matchIds === undefined ? null : matchIds)) continue;
193
- children.appendChild(renderSession(root.workers[i], { worker: true, current: current.has(root.workers[i].id) }));
194
- }
195
- wrapper.appendChild(children);
196
275
  return wrapper;
197
276
  }
198
277