clay-server 3.8.1 → 4.0.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 (189) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/builtin-mates.js +6 -6
  3. package/lib/claude-hook-installer.js +15 -0
  4. package/lib/clay-history-mcp-server.js +62 -297
  5. package/lib/daemon.js +1 -1
  6. package/lib/debate-model-selection.js +119 -0
  7. package/lib/home-debate-tool-policy.js +164 -0
  8. package/lib/home-response-notifications.js +26 -0
  9. package/lib/mate-creation-mcp-server.js +55 -0
  10. package/lib/mate-ready-creation.js +95 -0
  11. package/lib/mates.js +2 -0
  12. package/lib/notes.js +2 -1
  13. package/lib/project-ask-user.js +64 -0
  14. package/lib/project-capsule-catalog.js +101 -0
  15. package/lib/project-connection.js +4 -0
  16. package/lib/project-debate-proposal.js +133 -17
  17. package/lib/project-debate.js +214 -110
  18. package/lib/project-delegated-follow-up.js +84 -0
  19. package/lib/project-delegated-session.js +53 -0
  20. package/lib/project-filesystem.js +7 -9
  21. package/lib/project-knowledge.js +1 -0
  22. package/lib/project-mate-creation-proposal.js +120 -0
  23. package/lib/project-memory.js +14 -7
  24. package/lib/project-models.js +157 -32
  25. package/lib/project-session-notes.js +2 -2
  26. package/lib/project-session-pair.js +8 -8
  27. package/lib/project-sessions.js +66 -51
  28. package/lib/project-vendor-login.js +397 -0
  29. package/lib/project-worker-proposal.js +25 -25
  30. package/lib/project-workspace-query.js +144 -0
  31. package/lib/project.js +349 -102
  32. package/lib/public/app.js +85 -42
  33. package/lib/public/css/avatar-imprints.css +0 -1
  34. package/lib/public/css/base.css +4 -0
  35. package/lib/public/css/capsule-ui.css +389 -0
  36. package/lib/public/css/command-palette.css +306 -6
  37. package/lib/public/css/home-capsule-library.css +243 -0
  38. package/lib/public/css/home-capsule-source.css +192 -0
  39. package/lib/public/css/home-debate-live.css +146 -0
  40. package/lib/public/css/home-debate-planning.css +125 -0
  41. package/lib/public/css/home-debates-archive.css +281 -0
  42. package/lib/public/css/home-hub.css +896 -405
  43. package/lib/public/css/home-mate-creation.css +34 -0
  44. package/lib/public/css/home-mate-model-picker.css +131 -0
  45. package/lib/public/css/home-mate-settings.css +344 -0
  46. package/lib/public/css/home-session-actions.css +265 -0
  47. package/lib/public/css/home-sidebar.css +667 -0
  48. package/lib/public/css/icon-strip.css +1 -2
  49. package/lib/public/css/mates.css +7 -34
  50. package/lib/public/css/pane.css +1 -1
  51. package/lib/public/css/playbook.css +0 -80
  52. package/lib/public/css/sticky-notes.css +53 -117
  53. package/lib/public/css/tui-attention.css +0 -44
  54. package/lib/public/css/workspace-assignment.css +45 -0
  55. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  56. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  57. package/lib/public/index.html +93 -74
  58. package/lib/public/modules/app-connection.js +10 -1
  59. package/lib/public/modules/app-dm.js +43 -160
  60. package/lib/public/modules/app-home-hub.js +328 -585
  61. package/lib/public/modules/app-message-cards.js +242 -0
  62. package/lib/public/modules/app-message-router.js +84 -0
  63. package/lib/public/modules/app-messages.js +102 -37
  64. package/lib/public/modules/app-notifications.js +8 -119
  65. package/lib/public/modules/app-projects.js +9 -1
  66. package/lib/public/modules/app-rendering.js +21 -602
  67. package/lib/public/modules/avatar-imprint.js +11 -8
  68. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  69. package/lib/public/modules/chat-render-runtime.js +198 -0
  70. package/lib/public/modules/command-palette.js +225 -436
  71. package/lib/public/modules/dm-render.js +82 -0
  72. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  73. package/lib/public/modules/home-capsule-library.js +146 -0
  74. package/lib/public/modules/home-capsule-source.js +383 -0
  75. package/lib/public/modules/home-chat-identity.js +35 -0
  76. package/lib/public/modules/home-chat-scroll.js +77 -0
  77. package/lib/public/modules/home-chat-stream-state.js +34 -0
  78. package/lib/public/modules/home-composer-focus.js +25 -0
  79. package/lib/public/modules/home-conversations-sheet.js +179 -0
  80. package/lib/public/modules/home-debate-activity.js +48 -0
  81. package/lib/public/modules/home-debate-controls.js +295 -0
  82. package/lib/public/modules/home-debate-launch.js +41 -0
  83. package/lib/public/modules/home-debate-live.js +284 -0
  84. package/lib/public/modules/home-debate-models.js +73 -0
  85. package/lib/public/modules/home-debate-planning.js +335 -0
  86. package/lib/public/modules/home-debates-archive.js +279 -0
  87. package/lib/public/modules/home-dock-resize.js +74 -0
  88. package/lib/public/modules/home-dock.js +446 -0
  89. package/lib/public/modules/home-mate-chat.js +496 -0
  90. package/lib/public/modules/home-mate-creation.js +109 -0
  91. package/lib/public/modules/home-mate-model-picker.js +281 -0
  92. package/lib/public/modules/home-mate-selection.js +38 -0
  93. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  94. package/lib/public/modules/home-mate-settings.js +383 -0
  95. package/lib/public/modules/home-session-actions.js +373 -0
  96. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  97. package/lib/public/modules/home-shell.js +9 -0
  98. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  99. package/lib/public/modules/home-sidebar.js +158 -0
  100. package/lib/public/modules/home-sub-surface.js +19 -0
  101. package/lib/public/modules/home-surface-boot.js +68 -0
  102. package/lib/public/modules/home-surface.js +141 -0
  103. package/lib/public/modules/home-tools.js +271 -0
  104. package/lib/public/modules/markdown.js +50 -2
  105. package/lib/public/modules/mate-management.js +86 -0
  106. package/lib/public/modules/mate-wizard.js +0 -7
  107. package/lib/public/modules/mention.js +17 -19
  108. package/lib/public/modules/pane-bridge.js +9 -0
  109. package/lib/public/modules/profile.js +4 -5
  110. package/lib/public/modules/project-settings.js +34 -28
  111. package/lib/public/modules/project-switcher.js +11 -5
  112. package/lib/public/modules/project-workspace-assignment.js +23 -0
  113. package/lib/public/modules/search-clay-chat.js +402 -0
  114. package/lib/public/modules/server-settings.js +14 -12
  115. package/lib/public/modules/session-actions.js +1 -1
  116. package/lib/public/modules/sidebar-mates.js +56 -552
  117. package/lib/public/modules/sidebar-mobile.js +7 -64
  118. package/lib/public/modules/sidebar-presence.js +37 -0
  119. package/lib/public/modules/split-pair-ui.js +13 -13
  120. package/lib/public/modules/split-view.js +7 -0
  121. package/lib/public/modules/sticky-notes.js +7 -6
  122. package/lib/public/modules/tool-input-composition.js +48 -0
  123. package/lib/public/modules/tool-llm-status.js +167 -0
  124. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  125. package/lib/public/modules/tool-renderer-chart.js +158 -0
  126. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  127. package/lib/public/modules/tool-renderer.js +464 -0
  128. package/lib/public/modules/tool-runtime.js +309 -0
  129. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  130. package/lib/public/modules/tool-ui-tree.js +21 -0
  131. package/lib/public/modules/tui-attention.js +10 -0
  132. package/lib/public/modules/user-settings.js +16 -23
  133. package/lib/public/modules/vendor-login.js +287 -0
  134. package/lib/public/modules/worker-proposal.js +2 -2
  135. package/lib/public/modules/workspace-assignment-card.js +149 -0
  136. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  137. package/lib/public/style.css +13 -2
  138. package/lib/runtime-env.js +65 -0
  139. package/lib/sdk-bridge.js +120 -32
  140. package/lib/sdk-message-processor.js +10 -6
  141. package/lib/server-home-capsule-creation.js +36 -0
  142. package/lib/server-home-chat-events.js +287 -0
  143. package/lib/server-home-chat.js +518 -0
  144. package/lib/server-home-clay-entry.js +65 -0
  145. package/lib/server-home-clay-session-links.js +26 -0
  146. package/lib/server-home-debate-planning.js +214 -0
  147. package/lib/server-home-debates.js +137 -0
  148. package/lib/server-home-mate-creation.js +261 -0
  149. package/lib/server-home-models.js +241 -0
  150. package/lib/server-home-preferences.js +61 -0
  151. package/lib/server-mates.js +37 -0
  152. package/lib/server-tools.js +377 -0
  153. package/lib/server.js +111 -59
  154. package/lib/session-pair-mcp-server.js +6 -6
  155. package/lib/sessions.js +18 -3
  156. package/lib/tool-capsule-source.js +142 -0
  157. package/lib/tool-control-mcp-server.js +126 -0
  158. package/lib/tool-llm.js +48 -0
  159. package/lib/tool-storage.js +77 -0
  160. package/lib/tool-ui-spec-advanced.js +82 -0
  161. package/lib/tool-ui-spec.js +261 -0
  162. package/lib/tools-registry.js +286 -0
  163. package/lib/users-home-dock-preferences.js +77 -0
  164. package/lib/users-home-surface-preferences.js +99 -0
  165. package/lib/users-preferences.js +4 -7
  166. package/lib/users.js +12 -0
  167. package/lib/whats-new-content.js +3 -3
  168. package/lib/workspace-assignment-service.js +420 -0
  169. package/lib/workspace-query-mcp-server.js +154 -0
  170. package/lib/workspace-query-service.js +492 -0
  171. package/lib/ws-schema.js +93 -12
  172. package/lib/yoke/adapters/acp.js +2 -1
  173. package/lib/yoke/adapters/antigravity.js +2 -1
  174. package/lib/yoke/adapters/claude-worker.js +11 -4
  175. package/lib/yoke/adapters/claude.js +73 -43
  176. package/lib/yoke/adapters/codex.js +40 -10
  177. package/lib/yoke/adapters/kiro.js +2 -1
  178. package/lib/yoke/claude-user-input.js +38 -0
  179. package/lib/yoke/codex-app-server.js +20 -2
  180. package/lib/yoke/codex-user-input.js +64 -0
  181. package/lib/yoke/complete-once.js +164 -0
  182. package/lib/yoke/index.js +5 -1
  183. package/lib/yoke/interface.js +19 -0
  184. package/lib/yoke/mcp-bridge-server.js +2 -6
  185. package/lib/yoke/user-input.js +254 -0
  186. package/package.json +3 -2
  187. package/lib/public/css/home-chat.css +0 -380
  188. package/lib/public/modules/home-chat.js +0 -438
  189. package/lib/server-clay-home.js +0 -245
@@ -1,18 +1,15 @@
1
1
  // sidebar-mates.js - User/mate strip, DM picker, context menus, tooltips, presence
2
2
  // Extracted from sidebar.js (PR-37)
3
3
 
4
- import { userAvatarUrl, mateAvatarUrl } from './avatar.js';
4
+ import { userAvatarUrl } from './avatar.js';
5
5
  import { escapeHtml } from './utils.js';
6
6
  import { iconHtml, refreshIcons } from './icons.js';
7
- import { showMateProfilePopover } from './profile.js';
8
7
  import { store } from './store.js';
9
8
  import { getWs } from './ws-ref.js';
10
9
  import { closeProjectCtxMenu } from './sidebar-projects.js';
11
10
  import { spawnDustParticles } from './sidebar.js';
12
11
  import { openDm } from './app-dm.js';
13
- import { openMateWizard } from './mate-wizard.js';
14
- import { openUserSettings } from './user-settings.js';
15
- import { getCachedProjects } from './app-projects.js';
12
+ import { showMateManagementMenu, closeMateManagementMenu } from './mate-management.js';
16
13
 
17
14
  function sendWs(msg) {
18
15
  var ws = getWs();
@@ -32,13 +29,33 @@ var cachedDmRemovedUsers = {};
32
29
  var cachedMates = [];
33
30
  var activeMentionMateIds = {};
34
31
 
32
+ export function getActiveMentionMateIds() {
33
+ return activeMentionMateIds;
34
+ }
35
+
35
36
  export function setMentionActive(mateId, active) {
36
37
  if (active) { activeMentionMateIds[mateId] = true; }
37
38
  else { delete activeMentionMateIds[mateId]; }
39
+ notifyMentionListeners();
38
40
  }
39
41
 
40
42
  export function clearAllMentionActive() {
41
43
  activeMentionMateIds = {};
44
+ notifyMentionListeners();
45
+ }
46
+
47
+ // Mates render on the home hub now, so their mention state has to reach a
48
+ // renderer that no longer owns it. Listeners are registered by app.js.
49
+ var mentionListeners = [];
50
+
51
+ export function onMentionActiveChange(listener) {
52
+ mentionListeners.push(listener);
53
+ }
54
+
55
+ function notifyMentionListeners() {
56
+ for (var i = 0; i < mentionListeners.length; i++) {
57
+ try { mentionListeners[i](); } catch (e) { /* listener must not break the strip */ }
58
+ }
42
59
  }
43
60
  var _lastUserStripJson = "";
44
61
 
@@ -79,10 +96,11 @@ export function showIconTooltip(el, text) {
79
96
  });
80
97
  }
81
98
 
82
- export function showIconTooltipHtml(el, html) {
99
+ export function showIconTooltipHtml(el, html, options) {
83
100
  hideIconTooltip();
101
+ options = options || {};
84
102
  var tip = document.createElement("div");
85
- tip.className = "icon-strip-tooltip";
103
+ tip.className = "icon-strip-tooltip" + (options.className ? " " + options.className : "");
86
104
  tip.style.whiteSpace = "normal";
87
105
  tip.style.maxWidth = "260px";
88
106
  tip.innerHTML = html;
@@ -91,7 +109,14 @@ export function showIconTooltipHtml(el, html) {
91
109
 
92
110
  requestAnimationFrame(function () {
93
111
  var rect = el.getBoundingClientRect();
94
- tip.style.top = (rect.top + rect.height / 2 - tip.offsetHeight / 2) + "px";
112
+ if (options.placement === "bottom") {
113
+ var left = rect.left + rect.width / 2 - tip.offsetWidth / 2;
114
+ left = Math.max(12, Math.min(left, window.innerWidth - tip.offsetWidth - 12));
115
+ tip.style.left = left + "px";
116
+ tip.style.top = (rect.bottom + 8) + "px";
117
+ } else {
118
+ tip.style.top = (rect.top + rect.height / 2 - tip.offsetHeight / 2) + "px";
119
+ }
95
120
  tip.classList.add("visible");
96
121
  });
97
122
  }
@@ -109,6 +134,7 @@ export function closeUserCtxMenu() {
109
134
  userCtxMenu = null;
110
135
  }
111
136
  document.removeEventListener("click", handleUserCtxOutsideClick, true);
137
+ closeMateManagementMenu();
112
138
  }
113
139
 
114
140
  function showUserCtxMenu(anchorEl, user) {
@@ -166,97 +192,17 @@ function handleUserCtxOutsideClick(e) {
166
192
  }
167
193
  }
168
194
 
169
- function showMateCtxMenu(anchorEl, mate) {
170
- // Primary mates cannot be edited or removed
171
- if (mate.primary) return;
172
-
173
- closeUserCtxMenu();
174
- if (closeProjectCtxMenu) closeProjectCtxMenu();
175
-
176
- var menu = document.createElement("div");
177
- menu.className = "project-ctx-menu";
178
-
179
- // Edit Profile item
180
- var editItem = document.createElement("button");
181
- editItem.className = "project-ctx-item";
182
- editItem.innerHTML = iconHtml("edit-2") + " <span>Edit Profile</span>";
183
- editItem.addEventListener("click", function (e) {
184
- e.stopPropagation();
185
- closeUserCtxMenu();
186
- showMateProfilePopover(anchorEl, mate, function (updates) {
187
- sendWs({ type: "mate_update", mateId: mate.id, updates: updates });
188
- });
189
- });
190
- menu.appendChild(editItem);
191
-
192
- var removeItem = document.createElement("button");
193
- removeItem.className = "project-ctx-item";
194
- removeItem.innerHTML = iconHtml("star-off") + " <span>Remove from favorites</span>";
195
- removeItem.addEventListener("click", function (e) {
196
- e.stopPropagation();
197
- closeUserCtxMenu();
198
- // Spawn dust particles at the mate icon position
199
- var iconRect = anchorEl.getBoundingClientRect();
200
- if (spawnDustParticles) spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
201
- sendWs({ type: "dm_remove_favorite", targetUserId: mate.id });
195
+ export function showMateCtxMenu(anchorEl, mate) {
196
+ showMateManagementMenu(anchorEl, mate, {
197
+ beforeOpen: function () {
198
+ closeUserCtxMenu();
199
+ if (closeProjectCtxMenu) closeProjectCtxMenu();
200
+ },
201
+ onRemoved: function () {
202
+ var iconRect = anchorEl.getBoundingClientRect();
203
+ if (spawnDustParticles) spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
204
+ },
202
205
  });
203
- menu.appendChild(removeItem);
204
-
205
- document.body.appendChild(menu);
206
- userCtxMenu = menu;
207
- refreshIcons();
208
-
209
- requestAnimationFrame(function () {
210
- var rect = anchorEl.getBoundingClientRect();
211
- menu.style.position = "fixed";
212
- menu.style.left = (rect.right + 6) + "px";
213
- menu.style.top = rect.top + "px";
214
- var menuRect = menu.getBoundingClientRect();
215
- if (menuRect.right > window.innerWidth - 8) {
216
- menu.style.left = (rect.left - menuRect.width - 6) + "px";
217
- }
218
- if (menuRect.bottom > window.innerHeight - 8) {
219
- menu.style.top = (window.innerHeight - menuRect.height - 8) + "px";
220
- }
221
- });
222
-
223
- setTimeout(function () {
224
- document.addEventListener("click", handleUserCtxOutsideClick, true);
225
- }, 0);
226
- }
227
-
228
- var _lastSidebarPresenceIds = [];
229
- export function renderSidebarPresence(onlineUsers) {
230
- var container = document.getElementById("sidebar-presence");
231
- if (!container) return;
232
- if (!onlineUsers || onlineUsers.length < 2) {
233
- if (_lastSidebarPresenceIds.length > 0) {
234
- _lastSidebarPresenceIds = [];
235
- container.innerHTML = "";
236
- }
237
- return;
238
- }
239
- // Skip re-render if same users
240
- var newIds = onlineUsers.map(function (u) { return u.id; }).sort();
241
- if (newIds.length === _lastSidebarPresenceIds.length && newIds.every(function (id, i) { return id === _lastSidebarPresenceIds[i]; })) return;
242
- _lastSidebarPresenceIds = newIds;
243
- container.innerHTML = "";
244
- var maxShow = 4;
245
- for (var i = 0; i < Math.min(onlineUsers.length, maxShow); i++) {
246
- var ou = onlineUsers[i];
247
- var img = document.createElement("img");
248
- img.className = "sidebar-presence-avatar";
249
- img.src = presenceAvatarUrl(ou);
250
- img.alt = ou.displayName;
251
- img.dataset.tip = ou.displayName + " (@" + ou.username + ")";
252
- container.appendChild(img);
253
- }
254
- if (onlineUsers.length > maxShow) {
255
- var more = document.createElement("span");
256
- more.className = "sidebar-presence-more";
257
- more.textContent = "+" + (onlineUsers.length - maxShow);
258
- container.appendChild(more);
259
- }
260
206
  }
261
207
 
262
208
  // Presence avatar URL helper
@@ -365,135 +311,6 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
365
311
  })(others[i]);
366
312
  }
367
313
 
368
- // Build mate project status lookup from project list
369
- var mateProjectStatus = {};
370
- var _projList = getCachedProjects() || [];
371
- if (_projList.length) {
372
- var allProjects = _projList;
373
- for (var pi = 0; pi < allProjects.length; pi++) {
374
- if (allProjects[pi].isMate) {
375
- mateProjectStatus[allProjects[pi].slug] = allProjects[pi];
376
- }
377
- }
378
- }
379
-
380
- // Render mates: only favorited or unread, regardless of mode. Previously
381
- // single-user mode short-circuited to "show all", which meant users who
382
- // never engaged with Mates still saw 6 permanent avatars in the rail
383
- // with no way to thin them out (#341). Applying the same filter as
384
- // multi-user lets users curate the icon strip via favorites; the full
385
- // mate list is still reachable from the DM picker.
386
- var favoriteMates = cachedMates.filter(function (m) {
387
- if (m.archived) return false;
388
- // Clay is the host agent reachable only via the Home button — never
389
- // shown alongside regular mates in the sidebar list.
390
- if (m.builtinKey === "clay") return false;
391
- if (cachedDmRemovedUsers[m.id]) return false;
392
- if (cachedDmFavorites.indexOf(m.id) !== -1) return true;
393
- if (cachedDmUnread[m.id] && cachedDmUnread[m.id] > 0) return true;
394
- return false;
395
- });
396
- var sortedMates = favoriteMates.sort(function (a, b) {
397
- var aBuiltin = a.builtinKey ? 1 : 0;
398
- var bBuiltin = b.builtinKey ? 1 : 0;
399
- if (aBuiltin !== bBuiltin) return bBuiltin - aBuiltin;
400
- return (a.createdAt || 0) - (b.createdAt || 0);
401
- });
402
- for (var mi = 0; mi < sortedMates.length; mi++) {
403
- (function (mate) {
404
- var mp = mate.profile || {};
405
- var mateSlug = "mate-" + mate.id;
406
- var mateProj = mateProjectStatus[mateSlug] || {};
407
- var isActive = mate.id === currentDmUserId;
408
- var el = document.createElement("div");
409
- el.className = "icon-strip-user icon-strip-mate";
410
- el.dataset.userId = mate.id;
411
- el.dataset.mateSlug = mateSlug;
412
- if (isActive) el.classList.add("active");
413
-
414
- // Pending permission shake
415
- if (mateProj.pendingPermissions > 0 && !isActive) {
416
- el.classList.add("has-pending-perm");
417
- }
418
-
419
- var pill = document.createElement("span");
420
- pill.className = "icon-strip-pill";
421
- el.appendChild(pill);
422
-
423
- var avatar = document.createElement("img");
424
- avatar.className = "icon-strip-user-avatar" + (mate.primary ? " icon-strip-primary-mate" : "");
425
- avatar.src = mateAvatarUrl(mate, 34);
426
- avatar.alt = mp.displayName || mate.name || "Mate";
427
- var mateColor = (mp.avatarColor) || mate.avatarColor || "#5857fc";
428
- avatar.style.background = mateColor + "30";
429
- el.appendChild(avatar);
430
-
431
- // Processing status dot (IO blink) - top-left
432
- var statusDot = document.createElement("span");
433
- statusDot.className = "icon-strip-status";
434
- var isMentionActive = !!activeMentionMateIds[mate.id];
435
- if (mateProj.isProcessing || isMentionActive) statusDot.classList.add("processing");
436
- if (isMentionActive) el.classList.add("mention-active");
437
- el.appendChild(statusDot);
438
-
439
- // Mate badge (bot icon)
440
- var mateBadge = document.createElement("span");
441
- mateBadge.className = "icon-strip-user-mate-badge";
442
- mateBadge.innerHTML = iconHtml("bot");
443
- el.appendChild(mateBadge);
444
-
445
- var badge = document.createElement("span");
446
- badge.className = "icon-strip-user-badge";
447
- badge.dataset.userId = mate.id;
448
- el.appendChild(badge);
449
-
450
- // Restore unread badge if cached
451
- var unreadCount = cachedDmUnread[mate.id] || 0;
452
- if (unreadCount > 0 && !isActive) {
453
- badge.textContent = unreadCount > 99 ? "99+" : String(unreadCount);
454
- badge.classList.add("has-unread");
455
- }
456
-
457
- // Tooltip
458
- var displayName = mp.displayName || mate.name || "New Mate";
459
- var mateVendor = mate.vendor || "claude";
460
- var vendorLabels = {
461
- claude: "Claude Code",
462
- codex: "OpenAI Codex",
463
- antigravity: "Antigravity CLI",
464
- opencode: "OpenCode",
465
- kiro: "Kiro CLI",
466
- };
467
- el.addEventListener("mouseenter", function () {
468
- var html = '<div style="font-weight:600">' + escapeHtml(displayName);
469
- if (mate.primary) {
470
- html += ' <span style="font-size:10px;font-weight:600;color:#00b894;background:rgba(0,184,148,0.1);padding:1px 5px;border-radius:3px;margin-left:4px">SYSTEM</span>';
471
- }
472
- html += '</div>';
473
- if (mate.bio) {
474
- html += '<div style="font-weight:400;font-size:12px;color:var(--text-secondary);margin-top:2px">' + escapeHtml(mate.bio) + '</div>';
475
- }
476
- var vendorLabel = vendorLabels[mateVendor] || mateVendor;
477
- html += '<div style="font-size:11px;color:var(--text-dimmer);margin-top:3px">Powered by ' + escapeHtml(vendorLabel) + '</div>';
478
- showIconTooltipHtml(el, html);
479
- });
480
- el.addEventListener("mouseleave", hideIconTooltip);
481
-
482
- // Click: open DM with mate
483
- el.addEventListener("click", function () {
484
- if (openDm) openDm(mate.id);
485
- });
486
-
487
- // Right-click: context menu for mate
488
- el.addEventListener("contextmenu", function (e) {
489
- e.preventDefault();
490
- e.stopPropagation();
491
- showMateCtxMenu(el, mate);
492
- });
493
-
494
- container.appendChild(el);
495
- })(sortedMates[mi]);
496
- }
497
314
 
498
315
  // Show container if we have mates even with no other users
499
316
  if (cachedMates.length > 0) {
@@ -508,7 +325,7 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
508
325
  e.stopPropagation();
509
326
  toggleDmUserPicker(addBtn);
510
327
  });
511
- addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add user or create mate"); });
328
+ addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add user"); });
512
329
  addBtn.addEventListener("mouseleave", hideIconTooltip);
513
330
  container.appendChild(addBtn);
514
331
  refreshIcons();
@@ -525,17 +342,12 @@ function toggleDmUserPicker(anchorEl) {
525
342
  picker.className = "dm-user-picker";
526
343
  picker.id = "dm-user-picker";
527
344
 
528
- // Mates enabled flag is consulted up here so the search input can drop
529
- // mates from its placeholder copy when the section is hidden.
530
- var matesEnabled = store.get('matesEnabled') !== false;
531
-
532
345
  // Search input
533
346
  var searchInput = document.createElement("input");
534
347
  searchInput.className = "dm-user-picker-search";
535
348
  searchInput.type = "text";
536
- searchInput.placeholder = matesEnabled
537
- ? "Search mates and users..."
538
- : "Search users...";
349
+ searchInput.placeholder = "Search users...";
350
+ searchInput.setAttribute("aria-label", "Search users");
539
351
  picker.appendChild(searchInput);
540
352
 
541
353
  // User list element (appended later, after USERS label)
@@ -595,323 +407,15 @@ function toggleDmUserPicker(anchorEl) {
595
407
  }
596
408
  }
597
409
 
598
- // --- Layout depends on whether Mates is enabled ---
599
- // When enabled: Mates section first, then Users. Mates list is fully
600
- // rendered with delete buttons, create-mate entry, etc.
601
- // When disabled: Users first, then a Mates discovery promo (animated
602
- // avatar marquee + value prop + a single CTA that deep-links into
603
- // User Settings → Mates). We deliberately don't render the actual
604
- // mate list when off — showing real mates with greyed-out trash icons
605
- // is visually heavy and contradicts the user's choice to hide them.
606
- // (matesEnabled is computed earlier near the search input.)
607
-
608
- // matesListEl + renderMatesList are only used in the enabled layout.
609
- // Declared up-front so the search handler can reference renderMatesList
610
- // safely; renderMatesList stays a no-op in disabled mode.
611
- var matesListEl = null;
612
- var renderMatesList = function () {};
613
-
614
- function renderMatesEnabledSection() {
615
- var matesSectionLabel = document.createElement("div");
616
- matesSectionLabel.className = "dm-user-picker-section";
617
- matesSectionLabel.textContent = "Mates";
618
- picker.appendChild(matesSectionLabel);
619
-
620
- matesListEl = document.createElement("div");
621
- matesListEl.className = "dm-user-picker-list dm-mates-list";
622
- picker.appendChild(matesListEl);
623
-
624
- // Update scroll gradient hint
625
- function updateMatesScrollHint() {
626
- var isOverflow = matesListEl.scrollHeight > matesListEl.clientHeight + 2;
627
- if (!isOverflow) {
628
- matesListEl.classList.add("no-overflow");
629
- matesListEl.classList.remove("scrolled-bottom");
630
- return;
631
- }
632
- matesListEl.classList.remove("no-overflow");
633
- var atBottom = matesListEl.scrollTop + matesListEl.clientHeight >= matesListEl.scrollHeight - 4;
634
- if (atBottom) {
635
- matesListEl.classList.add("scrolled-bottom");
636
- } else {
637
- matesListEl.classList.remove("scrolled-bottom");
638
- }
639
- }
640
- matesListEl.addEventListener("scroll", updateMatesScrollHint);
641
-
642
- renderMatesList = function (filter) {
643
- matesListEl.innerHTML = "";
644
- var allMates = cachedMates || [];
645
- if (filter) {
646
- var lf = filter.toLowerCase();
647
- allMates = allMates.filter(function (m) {
648
- var name = (m.profile && m.profile.displayName) || m.name || "";
649
- return name.toLowerCase().indexOf(lf) !== -1;
650
- });
651
- }
652
- // Build unified list: installed builtins, deleted builtins, user-created
653
- var availBuiltins = store.get('cachedAvailableBuiltins') || [];
654
- var entries = [];
655
- // 1. Installed builtin mates
656
- for (var si = 0; si < allMates.length; si++) {
657
- if (allMates[si].builtinKey) entries.push({ type: "mate", data: allMates[si] });
658
- }
659
- // 2. Deleted builtins (only when not filtering)
660
- if (!filter) {
661
- for (var di = 0; di < availBuiltins.length; di++) {
662
- entries.push({ type: "deleted", data: availBuiltins[di] });
663
- }
664
- }
665
- // 3. User-created mates
666
- var userMates = allMates.filter(function (m) { return !m.builtinKey; });
667
- userMates.sort(function (a, b) { return (a.createdAt || 0) - (b.createdAt || 0); });
668
- for (var ui = 0; ui < userMates.length; ui++) {
669
- entries.push({ type: "mate", data: userMates[ui] });
670
- }
671
-
672
- for (var i = 0; i < entries.length; i++) {
673
- var entry = entries[i];
674
- if (entry.type === "deleted") {
675
- // Deleted builtin: show with "+ Add" button
676
- (function (b) {
677
- var bItem = document.createElement("div");
678
- bItem.className = "dm-user-picker-item dm-user-picker-builtin-item";
679
- bItem.style.opacity = "0.7";
680
- var bAv = document.createElement("img");
681
- bAv.className = "dm-user-picker-avatar";
682
- bAv.src = mateAvatarUrl({ avatarCustom: b.avatarCustom, avatarStyle: b.avatarStyle || "imprint", avatarSeed: b.displayName, id: b.key, avatarColor: b.avatarColor }, 28);
683
- bAv.alt = b.displayName;
684
- bItem.appendChild(bAv);
685
- var bNameWrap = document.createElement("div");
686
- bNameWrap.style.cssText = "flex:1;min-width:0;";
687
- var bName = document.createElement("span");
688
- bName.className = "dm-user-picker-name";
689
- bName.textContent = b.displayName;
690
- bNameWrap.appendChild(bName);
691
- var bBio = document.createElement("div");
692
- bBio.style.cssText = "font-size:11px;color:var(--text-dimmer);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
693
- bBio.textContent = b.bio || b.displayName;
694
- bNameWrap.appendChild(bBio);
695
- bItem.appendChild(bNameWrap);
696
- var bAddBtn = document.createElement("button");
697
- bAddBtn.style.cssText = "border:none;background:none;cursor:pointer;padding:2px 6px;color:var(--accent, #6366f1);font-size:12px;font-weight:600;white-space:nowrap;";
698
- bAddBtn.textContent = "+ Add";
699
- bAddBtn.title = "Re-add " + b.displayName;
700
- bAddBtn.addEventListener("click", function (e) {
701
- e.stopPropagation();
702
- sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
703
- closeDmUserPicker();
704
- });
705
- bItem.appendChild(bAddBtn);
706
- bItem.addEventListener("click", function () {
707
- sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
708
- closeDmUserPicker();
709
- });
710
- matesListEl.appendChild(bItem);
711
- })(entry.data);
712
- } else {
713
- // Normal mate
714
- (function (m) {
715
- var mp = m.profile || {};
716
- var isFav = cachedDmFavorites.indexOf(m.id) !== -1;
717
- var item = document.createElement("div");
718
- item.className = "dm-user-picker-item";
719
- if (isFav) item.classList.add("dm-picker-fav");
720
- var av = document.createElement("img");
721
- av.className = "dm-user-picker-avatar";
722
- av.src = mateAvatarUrl(m, 28);
723
- av.alt = mp.displayName || m.name || "Mate";
724
- item.appendChild(av);
725
- var nameWrap = document.createElement("div");
726
- nameWrap.style.cssText = "flex:1;min-width:0;";
727
- var name = document.createElement("span");
728
- name.className = "dm-user-picker-name";
729
- name.textContent = mp.displayName || m.name || "Mate";
730
- nameWrap.appendChild(name);
731
- if (m.bio) {
732
- var bio = document.createElement("div");
733
- bio.style.cssText = "font-size:11px;color:var(--text-dimmer);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
734
- bio.textContent = m.bio;
735
- nameWrap.appendChild(bio);
736
- }
737
- item.appendChild(nameWrap);
738
- // Delete button with inline confirm
739
- var delBtn = document.createElement("button");
740
- delBtn.className = "dm-picker-del-btn";
741
- delBtn.innerHTML = m.builtinKey ? iconHtml("minus-circle") : iconHtml("trash-2");
742
- delBtn.title = m.builtinKey ? "Remove mate" : "Delete mate";
743
- delBtn.addEventListener("click", function (e) {
744
- e.stopPropagation();
745
- var origHtml = item.innerHTML;
746
- item.innerHTML = "";
747
- item.style.justifyContent = "center";
748
- item.style.gap = "6px";
749
- var confirmMsg = document.createElement("span");
750
- confirmMsg.style.cssText = "font-size:12px;color:var(--text-dimmer);";
751
- confirmMsg.textContent = m.builtinKey ? "Remove? You can add back anytime." : "Delete permanently?";
752
- item.appendChild(confirmMsg);
753
- var yesBtn = document.createElement("button");
754
- yesBtn.style.cssText = "border:none;background:var(--danger,#e74c3c);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;";
755
- yesBtn.textContent = m.builtinKey ? "Remove" : "Delete";
756
- yesBtn.addEventListener("click", function (e2) {
757
- e2.stopPropagation();
758
- sendWs({ type: "mate_delete", mateId: m.id });
759
- closeDmUserPicker();
760
- });
761
- item.appendChild(yesBtn);
762
- var noBtn = document.createElement("button");
763
- noBtn.style.cssText = "border:1px solid var(--border);background:none;color:var(--text);padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;";
764
- noBtn.textContent = "Cancel";
765
- noBtn.addEventListener("click", function (e2) {
766
- e2.stopPropagation();
767
- item.innerHTML = origHtml;
768
- item.style.justifyContent = "";
769
- item.style.gap = "";
770
- refreshIcons();
771
- });
772
- item.appendChild(noBtn);
773
- });
774
- item.appendChild(delBtn);
775
- item.addEventListener("click", function () {
776
- if (openDm) openDm(m.id);
777
- if (!isFav) sendWs({ type: "dm_add_favorite", targetUserId: m.id });
778
- closeDmUserPicker();
779
- });
780
- matesListEl.appendChild(item);
781
- })(entry.data);
782
- }
783
- }
784
-
785
- if (entries.length === 0 && filter) {
786
- var emptyEl = document.createElement("div");
787
- emptyEl.className = "dm-user-picker-empty";
788
- emptyEl.textContent = "No mates found";
789
- matesListEl.appendChild(emptyEl);
790
- }
791
- refreshIcons();
792
- requestAnimationFrame(updateMatesScrollHint);
793
- };
794
-
795
- // Create Mate option
796
- var createMateEl = document.createElement("div");
797
- createMateEl.className = "dm-user-picker-create-mate";
798
- var hasCustomMates = (cachedMates || []).some(function (m) { return !m.builtinKey; });
799
- var createMateLabel = hasCustomMates ? "Create a Mate" : "Create a Mate for what you're doing";
800
- createMateEl.innerHTML = iconHtml("bot") + " <span>" + createMateLabel + "</span>";
801
- createMateEl.addEventListener("click", function () {
802
- closeDmUserPicker();
803
- if (openMateWizard) openMateWizard();
804
- });
805
- picker.appendChild(createMateEl);
806
-
807
- // Divider
808
- var divider = document.createElement("div");
809
- divider.style.borderTop = "1px solid var(--border, #333)";
810
- divider.style.margin = "4px 0";
811
- picker.appendChild(divider);
812
-
813
- // Users section
814
- var usersLabelEnabled = document.createElement("div");
815
- usersLabelEnabled.className = "dm-user-picker-section";
816
- usersLabelEnabled.textContent = "Users";
817
- picker.appendChild(usersLabelEnabled);
818
- picker.appendChild(listEl);
819
- }
820
-
821
- function renderMatesDisabledLayout() {
822
- // Users section first when Mates is off — they are the only thing
823
- // a user can actually act on, so they lead.
824
- var usersLabel = document.createElement("div");
825
- usersLabel.className = "dm-user-picker-section";
826
- usersLabel.textContent = "Users";
827
- picker.appendChild(usersLabel);
828
- picker.appendChild(listEl);
829
-
830
- // Divider
831
- var divider = document.createElement("div");
832
- divider.style.borderTop = "1px solid var(--border, #333)";
833
- divider.style.margin = "8px 0 4px";
834
- picker.appendChild(divider);
835
-
836
- // Mates discovery promo: animated avatar marquee + value prop + CTA.
837
- // The marquee uses real cached mate avatars purely as decoration; we
838
- // never attach click handlers, never show delete buttons, and never
839
- // render a list view — turning the section into a soft invitation
840
- // rather than a "list with everything greyed out" tease.
841
- var promo = document.createElement("div");
842
- promo.className = "dm-picker-mates-promo";
843
-
844
- var promoLabel = document.createElement("div");
845
- promoLabel.className = "dm-user-picker-section";
846
- promoLabel.textContent = "Mates";
847
- promo.appendChild(promoLabel);
848
-
849
- var marqueeWrap = document.createElement("div");
850
- marqueeWrap.className = "dm-picker-mates-marquee";
851
- var marqueeTrack = document.createElement("div");
852
- marqueeTrack.className = "dm-picker-mates-marquee-track";
853
- var marqueeSource = (cachedMates && cachedMates.length > 0)
854
- ? cachedMates.slice(0, 8)
855
- : (store.get('cachedAvailableBuiltins') || []).slice(0, 6);
856
- // Duplicate the avatar set so the keyframe loop wraps seamlessly.
857
- for (var copy = 0; copy < 2; copy++) {
858
- for (var mi = 0; mi < marqueeSource.length; mi++) {
859
- var src = marqueeSource[mi];
860
- var avEl = document.createElement("img");
861
- avEl.className = "dm-picker-mates-marquee-avatar";
862
- avEl.alt = "";
863
- avEl.setAttribute("aria-hidden", "true");
864
- if (src.id) {
865
- avEl.src = mateAvatarUrl(src, 32);
866
- } else {
867
- avEl.src = mateAvatarUrl({
868
- avatarCustom: src.avatarCustom,
869
- avatarStyle: src.avatarStyle || "imprint",
870
- avatarColor: src.avatarColor,
871
- avatarSeed: src.displayName,
872
- id: src.key,
873
- }, 32);
874
- }
875
- marqueeTrack.appendChild(avEl);
876
- }
877
- }
878
- marqueeWrap.appendChild(marqueeTrack);
879
- promo.appendChild(marqueeWrap);
880
-
881
- var promoText = document.createElement("div");
882
- promoText.className = "dm-picker-mates-promo-text";
883
- promoText.textContent = "Specialist AI teammates with long-term memory across sessions. Mention with @ for design, engineering, strategy, or marketing, or build your own.";
884
- promo.appendChild(promoText);
885
-
886
- var cta = document.createElement("button");
887
- cta.type = "button";
888
- cta.className = "dm-picker-mates-promo-cta";
889
- cta.textContent = "Click here to enable Mates";
890
- cta.addEventListener("click", function () {
891
- closeDmUserPicker();
892
- openUserSettings('us-mates');
893
- });
894
- promo.appendChild(cta);
895
-
896
- picker.appendChild(promo);
897
- }
898
-
899
- if (matesEnabled) {
900
- renderMatesEnabledSection();
901
- renderMatesList("");
902
- renderPickerList("");
903
- searchInput.addEventListener("input", function () {
904
- var val = searchInput.value;
905
- renderMatesList(val);
906
- renderPickerList(val);
907
- });
908
- } else {
909
- renderMatesDisabledLayout();
910
- renderPickerList("");
911
- searchInput.addEventListener("input", function () {
912
- renderPickerList(searchInput.value);
913
- });
914
- }
410
+ var usersLabel = document.createElement("div");
411
+ usersLabel.className = "dm-user-picker-section";
412
+ usersLabel.textContent = "Users";
413
+ picker.appendChild(usersLabel);
414
+ picker.appendChild(listEl);
415
+ renderPickerList("");
416
+ searchInput.addEventListener("input", function () {
417
+ renderPickerList(searchInput.value);
418
+ });
915
419
 
916
420
  // Focus search
917
421
  setTimeout(function () { searchInput.focus(); }, 50);