clay-server 5.2.0-beta.4 → 5.2.0-beta.6

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 (301) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +17 -14
  3. package/lib/canonical-clay-record-authority.js +15 -0
  4. package/lib/canonical-clay-scheduling-authority.js +19 -0
  5. package/lib/claude-hook-installer.js +1 -239
  6. package/lib/claude-jsonl-watcher.js +1 -204
  7. package/lib/clay-mate-chat-receipts.js +26 -0
  8. package/lib/clay-mate-chat.js +87 -0
  9. package/lib/clay-mate-knowledge-input.js +26 -0
  10. package/lib/clay-mate-knowledge.js +95 -0
  11. package/lib/clay-mate-lifecycle.js +95 -0
  12. package/lib/clay-product-control-input.js +33 -0
  13. package/lib/clay-product-control-tools.js +51 -0
  14. package/lib/clay-product-control.js +237 -0
  15. package/lib/clay-project-lifecycle.js +154 -0
  16. package/lib/clay-project-record-input.js +44 -0
  17. package/lib/clay-project-records.js +83 -0
  18. package/lib/clay-scheduling-input.js +21 -0
  19. package/lib/clay-scheduling-receipts.js +26 -0
  20. package/lib/clay-scheduling.js +119 -0
  21. package/lib/clay-screen-controls.js +96 -0
  22. package/lib/clay-skill-install.js +1 -33
  23. package/lib/clay-workspace-controls.js +83 -0
  24. package/lib/clay-workspace-input.js +17 -0
  25. package/lib/clay-workspace-receipts.js +26 -0
  26. package/lib/cli-sessions.js +1 -300
  27. package/lib/codex-defaults.js +1 -28
  28. package/lib/context-usage.js +3 -2
  29. package/lib/daemon-project-access.js +11 -0
  30. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  31. package/lib/daemon-project-lifecycle.js +262 -0
  32. package/lib/daemon-projects.js +1 -1
  33. package/lib/daemon.js +78 -276
  34. package/lib/default-ai-runtime.js +4 -8
  35. package/lib/email-mcp-server.js +1 -1
  36. package/lib/issues-service.js +29 -15
  37. package/lib/kiro-defaults.js +1 -22
  38. package/lib/knowledge-db-import.js +5 -4
  39. package/lib/knowledge-db-service.js +9 -6
  40. package/lib/knowledge-document-index.js +2 -1
  41. package/lib/knowledge-product-operations.js +46 -0
  42. package/lib/mate-creation-mcp-server.js +2 -0
  43. package/lib/mate-knowledge-canonical-access.js +10 -0
  44. package/lib/mate-knowledge-service.js +7 -4
  45. package/lib/mate-lifecycle-service.js +94 -0
  46. package/lib/mate-message-delivery.js +43 -0
  47. package/lib/mate-ready-creation.js +9 -1
  48. package/lib/mate-schedule-input.js +29 -0
  49. package/lib/mate-schedule-lifecycle.js +151 -0
  50. package/lib/mate-scheduling.js +103 -0
  51. package/lib/mates.js +4 -3
  52. package/lib/mcp-proxy.js +2 -32
  53. package/lib/mcp-query-bridge.js +1 -1
  54. package/lib/mcp-source-adapters.js +1 -91
  55. package/lib/mcp-source-context.js +1 -42
  56. package/lib/mcp-source-discovery.js +1 -169
  57. package/lib/mcp-source-plugins.js +1 -68
  58. package/lib/mcp-source-restrictions.js +1 -47
  59. package/lib/mcp-tool-policy.js +1 -26
  60. package/lib/notes-checklist.js +50 -0
  61. package/lib/notes.js +49 -3
  62. package/lib/os-user-diagnostics.js +2 -4
  63. package/lib/os-users.js +24 -70
  64. package/lib/project-admin-overview.js +51 -0
  65. package/lib/project-ask-user.js +4 -2
  66. package/lib/project-connection.js +8 -7
  67. package/lib/project-delegated-follow-up.js +2 -1
  68. package/lib/project-directory-claim.js +65 -0
  69. package/lib/project-file-operations.js +61 -0
  70. package/lib/project-filesystem.js +13 -87
  71. package/lib/project-full-access.js +23 -2
  72. package/lib/project-http.js +4 -4
  73. package/lib/project-knowledge.js +32 -18
  74. package/lib/project-lifecycle-operations.js +98 -0
  75. package/lib/project-lifecycle-process-witness.js +32 -0
  76. package/lib/project-lifecycle-runtime.js +50 -0
  77. package/lib/project-linux-access.js +22 -0
  78. package/lib/project-logs-canonical-clay.js +27 -0
  79. package/lib/project-logs-clay-query.js +16 -0
  80. package/lib/project-logs-schema.js +4 -2
  81. package/lib/project-logs-service.js +8 -7
  82. package/lib/project-logs.js +1 -0
  83. package/lib/project-loop.js +1 -0
  84. package/lib/project-mate-chat-control.js +55 -0
  85. package/lib/project-mate-creation-proposal.js +18 -3
  86. package/lib/project-mcp.js +2 -2
  87. package/lib/project-models.js +14 -31
  88. package/lib/project-notifications.js +74 -16
  89. package/lib/project-ownership.js +77 -0
  90. package/lib/project-pair-result-delivery.js +14 -3
  91. package/lib/project-pair-usage.js +12 -13
  92. package/lib/project-pending-message-queue.js +4 -1
  93. package/lib/project-product-control.js +87 -0
  94. package/lib/project-product-session-queue.js +46 -0
  95. package/lib/project-push-access.js +28 -0
  96. package/lib/project-scheduled-message-queue.js +30 -0
  97. package/lib/project-scheduled-messages.js +63 -64
  98. package/lib/project-scheduled-task-control.js +83 -0
  99. package/lib/project-scheduled-tasks.js +13 -2
  100. package/lib/project-scheduling-access.js +10 -0
  101. package/lib/project-session-actions.js +65 -0
  102. package/lib/project-session-delete.js +8 -1
  103. package/lib/project-session-github.js +47 -12
  104. package/lib/project-session-history-actions.js +167 -0
  105. package/lib/project-session-pair.js +1 -1
  106. package/lib/project-session-runtime-actions.js +64 -0
  107. package/lib/project-session-spawn.js +2 -3
  108. package/lib/project-session-vendor.js +55 -0
  109. package/lib/project-sessions.js +82 -267
  110. package/lib/project-user-message.js +31 -1
  111. package/lib/project-vendor-login.js +6 -36
  112. package/lib/project-workspace-operations.js +126 -0
  113. package/lib/project-workspace-query.js +9 -2
  114. package/lib/project.js +95 -3
  115. package/lib/public/app.js +1 -0
  116. package/lib/public/css/clay-tool-activity.css +51 -0
  117. package/lib/public/css/home-mate-creation.css +5 -0
  118. package/lib/public/css/messages.css +6 -2
  119. package/lib/public/css/overlays.css +4 -2
  120. package/lib/public/css/project-admin-overview.css +13 -0
  121. package/lib/public/css/session-folders.css +11 -0
  122. package/lib/public/css/session-github.css +3 -0
  123. package/lib/public/css/sticky-notes.css +61 -1
  124. package/lib/public/index.html +1 -1
  125. package/lib/public/modules/admin-settings-projects.js +6 -2
  126. package/lib/public/modules/app-connection.js +8 -1
  127. package/lib/public/modules/app-favicon.js +11 -2
  128. package/lib/public/modules/app-header.js +4 -2
  129. package/lib/public/modules/app-loop-ui.js +2 -0
  130. package/lib/public/modules/app-message-router.js +1 -1
  131. package/lib/public/modules/app-messages.js +54 -8
  132. package/lib/public/modules/app-notifications.js +1 -2
  133. package/lib/public/modules/app-panels.js +61 -123
  134. package/lib/public/modules/app-projects.js +5 -0
  135. package/lib/public/modules/app-rate-limit.js +7 -27
  136. package/lib/public/modules/chat-render-runtime.js +3 -0
  137. package/lib/public/modules/clay-owner-navigation.js +27 -0
  138. package/lib/public/modules/clay-tool-activity.js +140 -0
  139. package/lib/public/modules/clay-tool-presentation.js +338 -0
  140. package/lib/public/modules/command-palette.js +1 -1
  141. package/lib/public/modules/default-ai.js +11 -3
  142. package/lib/public/modules/home-debate-planning.js +5 -5
  143. package/lib/public/modules/home-mate-chat.js +8 -6
  144. package/lib/public/modules/home-mate-creation.js +73 -1
  145. package/lib/public/modules/mcp-connection-view.js +1 -1
  146. package/lib/public/modules/permission-control.js +5 -4
  147. package/lib/public/modules/personal-settings-dialog.js +7 -1
  148. package/lib/public/modules/project-access-recovery.js +17 -0
  149. package/lib/public/modules/project-admin-overview.js +21 -0
  150. package/lib/public/modules/scheduler.js +2 -0
  151. package/lib/public/modules/session-create-form.js +10 -10
  152. package/lib/public/modules/session-folder-layout.js +1 -1
  153. package/lib/public/modules/session-folder-view-menu.js +9 -0
  154. package/lib/public/modules/session-github.js +65 -8
  155. package/lib/public/modules/settings-defaults.js +27 -28
  156. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  157. package/lib/public/modules/sticky-note-markdown.js +72 -6
  158. package/lib/public/modules/sticky-notes-actions.js +30 -0
  159. package/lib/public/modules/sticky-notes-browser.js +9 -2
  160. package/lib/public/modules/sticky-notes-card.js +23 -7
  161. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  162. package/lib/public/modules/sticky-notes-editor.js +108 -44
  163. package/lib/public/modules/sticky-notes-shared.js +50 -2
  164. package/lib/public/modules/sticky-notes-state.js +66 -0
  165. package/lib/public/modules/sticky-notes.js +34 -51
  166. package/lib/public/modules/thinking-lifecycle.js +33 -3
  167. package/lib/public/modules/thinking-view.js +23 -1
  168. package/lib/public/modules/tools.js +36 -7
  169. package/lib/public/modules/tui-grab.js +1 -2
  170. package/lib/public/modules/user-settings-appearance.js +10 -3
  171. package/lib/public/modules/user-settings-editors.js +1 -1
  172. package/lib/public/modules/user-settings-layout.js +11 -2
  173. package/lib/public/modules/user-settings-preferences.js +6 -2
  174. package/lib/public/modules/user-settings-profile.js +12 -2
  175. package/lib/public/modules/user-settings.js +3 -2
  176. package/lib/public/modules/vendor-login-browser.js +5 -3
  177. package/lib/public/modules/vendor-login.js +2 -2
  178. package/lib/public/modules/vendor-priority.js +15 -1
  179. package/lib/public/muse-avatar.svg +4 -0
  180. package/lib/public/style.css +2 -0
  181. package/lib/safe-bash-commands.js +3 -45
  182. package/lib/scheduled-task-records.js +3 -1
  183. package/lib/scheduler.js +2 -0
  184. package/lib/sdk-bridge.js +334 -403
  185. package/lib/sdk-foreground-admission.js +16 -0
  186. package/lib/sdk-mcp-descriptors.js +2 -37
  187. package/lib/sdk-message-processor.js +52 -17
  188. package/lib/sdk-quota-snapshot.js +1 -1
  189. package/lib/sdk-response-recovery.js +1 -0
  190. package/lib/sdk-worker.js +2 -677
  191. package/lib/server-admin.js +63 -16
  192. package/lib/server-dm.js +1 -1
  193. package/lib/server-global-ws.js +10 -20
  194. package/lib/server-home-chat-events.js +7 -3
  195. package/lib/server-home-chat.js +4 -16
  196. package/lib/server-home-mate-creation.js +1 -0
  197. package/lib/server-home-mcp-consent.js +3 -3
  198. package/lib/server-mates.js +25 -67
  199. package/lib/server-owner-ui.js +77 -0
  200. package/lib/server-path-access.js +38 -0
  201. package/lib/server-project-removal.js +22 -0
  202. package/lib/server.js +100 -30
  203. package/lib/session-config-transition.js +45 -0
  204. package/lib/session-folders.js +7 -2
  205. package/lib/session-fork-history.js +21 -0
  206. package/lib/session-github.js +9 -2
  207. package/lib/session-hygiene.js +3 -35
  208. package/lib/session-permission-mode.js +4 -3
  209. package/lib/session-skill-scope.js +11 -0
  210. package/lib/session-visibility.js +5 -2
  211. package/lib/sessions.js +27 -89
  212. package/lib/terminal-manager.js +52 -0
  213. package/lib/tool-llm.js +1 -1
  214. package/lib/tui-transcript-index.js +1 -132
  215. package/lib/users-permissions.js +2 -8
  216. package/lib/users.js +4 -0
  217. package/lib/vendor-login-browser.js +1 -11
  218. package/lib/workspace-query-access.js +4 -1
  219. package/lib/ws-schema.js +8 -2
  220. package/lib/yoke/acp-agent-profiles.js +5 -3
  221. package/lib/yoke/adapters/antigravity.js +3 -14
  222. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  223. package/lib/yoke/adapters/claude.js +54 -50
  224. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  225. package/lib/yoke/adapters/codex.js +53 -111
  226. package/lib/yoke/adapters/kiro.js +3 -1
  227. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  228. package/lib/yoke/adapters/muse.js +122 -0
  229. package/lib/yoke/auth-events.js +17 -0
  230. package/lib/yoke/claude-hook-installer.js +267 -0
  231. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  232. package/lib/yoke/claude-maintenance.js +55 -0
  233. package/lib/yoke/claude-permission-patterns.js +53 -0
  234. package/lib/yoke/claude-response-recovery.js +155 -0
  235. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  236. package/lib/yoke/claude-worker-startup.js +28 -0
  237. package/lib/yoke/clay-skill-install.js +33 -0
  238. package/lib/yoke/cli-sessions.js +300 -0
  239. package/lib/yoke/codex-app-server.js +7 -12
  240. package/lib/yoke/codex-idle-recovery.js +81 -1
  241. package/lib/yoke/codex-recovery-admission.js +164 -0
  242. package/lib/yoke/configuration.js +13 -0
  243. package/lib/yoke/discovery.js +70 -0
  244. package/lib/yoke/errors.js +8 -0
  245. package/lib/yoke/host-environment.js +7 -0
  246. package/lib/yoke/index.js +9 -51
  247. package/lib/yoke/interface.js +116 -0
  248. package/lib/yoke/kiro-acp-server.js +5 -5
  249. package/lib/yoke/legacy-sessions.js +89 -0
  250. package/lib/yoke/login-protocol.js +25 -0
  251. package/lib/yoke/maintenance.js +12 -0
  252. package/lib/yoke/mcp-proxy.js +32 -0
  253. package/lib/yoke/mcp-source-adapters.js +91 -0
  254. package/lib/yoke/mcp-source-context.js +42 -0
  255. package/lib/yoke/mcp-source-discovery.js +169 -0
  256. package/lib/yoke/mcp-source-plugins.js +68 -0
  257. package/lib/yoke/mcp-source-restrictions.js +47 -0
  258. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  259. package/lib/yoke/model-preferences.js +6 -0
  260. package/lib/yoke/muse-artifacts.js +36 -0
  261. package/lib/yoke/muse-controls.js +50 -0
  262. package/lib/yoke/muse-input.js +64 -0
  263. package/lib/yoke/muse-items.js +157 -0
  264. package/lib/yoke/muse-policy.js +52 -0
  265. package/lib/yoke/muse-query.js +428 -0
  266. package/lib/yoke/muse-runtime.js +211 -0
  267. package/lib/yoke/muse-services.js +79 -0
  268. package/lib/yoke/muse-skills.js +79 -0
  269. package/lib/yoke/muse-turns.js +71 -0
  270. package/lib/yoke/options/acp.js +2 -0
  271. package/lib/yoke/options/antigravity.js +2 -0
  272. package/lib/yoke/options/claude.js +21 -0
  273. package/lib/yoke/options/codex-defaults.js +29 -0
  274. package/lib/yoke/options/codex-settings.js +21 -0
  275. package/lib/yoke/options/codex.js +6 -0
  276. package/lib/yoke/options/kiro-defaults.js +22 -0
  277. package/lib/yoke/options/kiro.js +5 -0
  278. package/lib/yoke/options/muse.js +5 -0
  279. package/lib/yoke/provider-contract.js +5 -0
  280. package/lib/yoke/providers/antigravity-executable.js +8 -0
  281. package/lib/yoke/providers/claude-executable.js +40 -0
  282. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  283. package/lib/yoke/providers/claude-process.js +97 -0
  284. package/lib/yoke/providers/claude-provision.js +75 -0
  285. package/lib/yoke/providers/codex-errors.js +8 -0
  286. package/lib/yoke/providers/muse-executable.js +17 -0
  287. package/lib/yoke/provisioning.js +6 -0
  288. package/lib/yoke/query-options.js +10 -0
  289. package/lib/yoke/response-recovery.js +41 -0
  290. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  291. package/lib/yoke/sdk-server-factory.js +7 -0
  292. package/lib/yoke/session-history.js +8 -0
  293. package/lib/yoke/session-tool-transport.js +34 -13
  294. package/lib/yoke/settings.js +23 -0
  295. package/lib/yoke/source-locations.js +4 -0
  296. package/lib/yoke/title-query.js +18 -7
  297. package/lib/yoke/tui-transcript-index.js +132 -0
  298. package/lib/yoke/usage.js +48 -0
  299. package/lib/yoke/user-input.js +13 -0
  300. package/lib/yoke/vendor-registry.js +81 -1
  301. package/package.json +3 -2
@@ -1,24 +1,21 @@
1
1
  // The sticky-note canvas: the board of floating cards over the conversation.
2
- //
3
2
  // This module owns the notes map, visibility, the badges, and the WebSocket
4
3
  // handlers. Card markup and interactions live in sticky-notes-card.js, text
5
- // editing in sticky-notes-editor.js, and the leaf helpers in
4
+ // editing in sticky-notes-editor.js, and leaf helpers in
6
5
  // sticky-notes-shared.js. The browse/index pane is sticky-notes-browser.js.
7
6
 
8
7
  import { refreshIcons, iconHtml } from './icons.js';
9
- import { renderMiniMarkdown } from './sticky-note-markdown.js';
10
- import { enhanceClayLogLinks } from './clay-log-links.js';
11
- import { isClosedNote, send, clampPos, clearNoteTimers, syncTitle } from './sticky-notes-shared.js';
8
+ import { isClosedNote, send, clampPos, clearNoteTimers, deleteNoteUiState } from './sticky-notes-shared.js';
12
9
  import { renderNote, closeColorPicker } from './sticky-notes-card.js';
13
10
  import { closeFormatToolbar, isFormatToolbarOpen } from './sticky-notes-editor.js';
14
11
  import { store } from './store.js';
12
+ import { reconcileNote, reconcileNoteResult } from './sticky-notes-state.js';
15
13
 
16
14
  var notes = new Map(); // id -> { data, el }
17
15
  var notesVisible = false;
18
16
  var notesResizeObserver = null;
19
17
 
20
- // The canvas shows open notes only. Closed notes leave the canvas and live in
21
- // the browser's Closed tab until they are reopened.
18
+ // The canvas shows open notes only. Closed notes live in the browser's Closed tab.
22
19
  function openCount() {
23
20
  var count = 0;
24
21
  notes.forEach(function (entry) {
@@ -259,9 +256,7 @@ export function handleNotesList(msg) {
259
256
  var container = document.getElementById("sticky-notes-container");
260
257
  if (!container) return;
261
258
 
262
- // Keyed reconciliation, never a clear-and-rebuild. A reconnect re-sends this
263
- // list unchanged; tearing every note down and recreating it made all the
264
- // floating cards vanish and reappear, and destroyed any open editor.
259
+ // Reconcile keyed cards in place so reconnects retain the active editor.
265
260
  var list = msg.notes || [];
266
261
  var seen = {};
267
262
  var created = false;
@@ -283,28 +278,27 @@ export function handleNotesList(msg) {
283
278
  created = true;
284
279
  }
285
280
 
286
- // Remove notes the server no longer lists.
281
+ // Remove subscriptions and delayed writes along with stale cards.
287
282
  var staleIds = [];
288
283
  notes.forEach(function (value, id) {
289
284
  if (!seen[id]) staleIds.push(id);
290
285
  });
291
286
  for (i = 0; i < staleIds.length; i++) {
292
287
  var stale = notes.get(staleIds[i]);
288
+ if (stale && stale.el && stale.el._stickyNoteDispose) stale.el._stickyNoteDispose();
293
289
  if (stale && stale.el && stale.el.parentNode) stale.el.parentNode.removeChild(stale.el);
290
+ clearNoteTimers(staleIds[i]);
291
+ deleteNoteUiState(staleIds[i]);
294
292
  notes.delete(staleIds[i]);
295
293
  }
296
294
 
297
- // renderNote already refreshes icons for the nodes it builds; an all-patch
298
- // pass introduces no new Lucide markup and must not trigger a document scan.
295
+ // An all-patch list adds no icon markup and needs no document-wide scan.
299
296
  if (created) refreshIcons();
300
297
 
301
298
  updateBadge();
302
299
  updateSidebarBadge();
303
300
  refreshBrowser();
304
301
 
305
- // Auto-show only when something is actually asking for attention. A board of
306
- // nothing but closed notes must not pop the canvas open. Temporary hiding is
307
- // explicit view state and must survive reconnects and list updates.
308
302
  if (openCount() > 0 && !notesVisible) {
309
303
  notesVisible = true;
310
304
  applyCanvasVisibility();
@@ -337,18 +331,13 @@ export function handleNoteCreated(msg) {
337
331
  syncFlightControls();
338
332
  }
339
333
 
340
- // Patch one existing note element in place from a server payload.
341
- //
342
- // Geometry, z-order, colour, and opacity are plain style writes and are always
343
- // applied. The rendered markdown is only replaced when the text actually
344
- // changed and the note is not being edited, so a geometry-only or z-only
345
- // update never destroys the user's caret, selection, or in-progress draft.
346
- // Returns true when icon markup was replaced, so the caller can decide whether
347
- // a Lucide pass is needed at all.
334
+ // Canonical content reconciles through the store; geometry patches independently
335
+ // so movement and stacking updates preserve the editor and its draft.
336
+ // Return whether the minimize control introduced fresh icon markup.
348
337
  function patchNote(entry, next) {
349
338
  if (!entry || !next) return false;
350
- var previous = entry.data || {};
351
339
  entry.data = next;
340
+ reconcileNote(next);
352
341
  var el = entry.el;
353
342
 
354
343
  el.style.left = next.x + "px";
@@ -358,26 +347,6 @@ function patchNote(entry, next) {
358
347
  el.style.zIndex = 100 + (next.zIndex || 0);
359
348
  el.dataset.color = next.color || "purple";
360
349
 
361
- var textarea = el.querySelector(".sticky-note-text");
362
- var rendered = el.querySelector(".sticky-note-rendered");
363
- var nextText = next.text || "";
364
- var editing = rendered === document.activeElement || textarea === document.activeElement;
365
- // A draft the user typed but has not committed must survive a redraw.
366
- var hasDraft = !!(textarea && textarea.value !== (previous.text || ""));
367
- var textChanged = nextText !== (previous.text || "");
368
- if (rendered && !editing && !hasDraft && textChanged) {
369
- if (textarea) textarea.value = nextText;
370
- if (nextText.trim()) {
371
- rendered.innerHTML = renderMiniMarkdown(nextText);
372
- enhanceClayLogLinks(rendered);
373
- rendered.classList.remove("is-empty");
374
- } else {
375
- rendered.innerHTML = "";
376
- rendered.classList.add("is-empty");
377
- }
378
- syncTitle(el, nextText);
379
- }
380
-
381
350
  if (typeof next.opacity === "number") {
382
351
  el.style.setProperty("--note-opacity", next.opacity);
383
352
  var slider = el.querySelector(".sticky-note-opacity-slider");
@@ -388,8 +357,6 @@ function patchNote(entry, next) {
388
357
  else el.classList.remove("hidden");
389
358
  syncFlightState(el, true);
390
359
 
391
- // The minimize button swaps its icon, which is the only path here that can
392
- // introduce fresh Lucide markup.
393
360
  var iconsReplaced = false;
394
361
  var wasMinimized = el.classList.contains("minimized");
395
362
  var minBtn = el.querySelector(".sticky-note-min-btn");
@@ -419,13 +386,18 @@ export function handleNoteUpdated(msg) {
419
386
  if (patchNote(entry, msg.note)) refreshIcons();
420
387
  reclampAllNotes();
421
388
 
422
- // A close or reopen arrives as an ordinary update, so the canvas count and
423
- // the browser both follow from the same message.
424
389
  updateBadge();
425
390
  updateSidebarBadge();
426
391
  refreshBrowser();
427
392
  }
428
393
 
394
+ export function handleNoteUpdateResult(msg) {
395
+ var entry = notes.get(msg.noteId);
396
+ if (!entry || !msg.requestId) return;
397
+ var canonical = reconcileNoteResult(msg);
398
+ if (canonical) { entry.data = canonical; refreshBrowser(); }
399
+ }
400
+
429
401
  export function handleNoteWritten(msg) {
430
402
  var entry = notes.get(msg.id);
431
403
  if (!entry) return;
@@ -437,11 +409,11 @@ export function handleNoteWritten(msg) {
437
409
  }, 2100);
438
410
  }
439
411
 
440
- // Defensive only. The server no longer deletes notes and never sends this, but
441
- // an older daemon on the other end of the socket still must not corrupt state.
442
412
  export function handleNoteDeleted(msg) {
443
413
  var entry = notes.get(msg.id);
414
+ if (entry && entry.el._stickyNoteDispose) entry.el._stickyNoteDispose();
444
415
  if (entry) entry.el.remove();
416
+ deleteNoteUiState(msg.id);
445
417
  clearNoteTimers(msg.id);
446
418
  notes.delete(msg.id);
447
419
  updateBadge();
@@ -467,3 +439,14 @@ function updateSidebarBadge() {
467
439
  export function isNotesVisible() {
468
440
  return notesVisible;
469
441
  }
442
+
443
+ // Project navigation must cancel writes and detach subscriptions before reuse.
444
+ export function resetStickyNotes() {
445
+ handleNotesList({ notes: [] });
446
+ closeFormatToolbar();
447
+ closeColorPicker();
448
+ }
449
+
450
+ store.subscribe(function (next, previous) {
451
+ if (next.currentSlug !== previous.currentSlug) resetStickyNotes();
452
+ });
@@ -27,6 +27,32 @@ function maybeScrollToBottom() {
27
27
  if (!store.get('replayingHistory')) scrollToBottom();
28
28
  }
29
29
 
30
+ function cancelThinkingRender() {
31
+ var frame = store.get('thinkingRenderFrame');
32
+ if (frame == null) return;
33
+ cancelAnimationFrame(frame);
34
+ store.set({ thinkingRenderFrame: null });
35
+ }
36
+
37
+ function flushThinkingRender() {
38
+ cancelThinkingRender();
39
+ var state = getThinkingState();
40
+ if (!state) return;
41
+ updateThinkingView(state.el, state.segmentText || state.latestText, state.detailsText);
42
+ }
43
+
44
+ function scheduleThinkingRender() {
45
+ if (store.get('replayingHistory') || store.get('thinkingRenderFrame') != null) return;
46
+ store.set({ thinkingRenderFrame: requestAnimationFrame(function () {
47
+ store.set({ thinkingRenderFrame: null });
48
+ var state = getThinkingState();
49
+ if (!state || !state.active) return;
50
+ keepActiveThinkingAtTail();
51
+ updateThinkingView(state.el, state.segmentText || state.latestText, state.detailsText);
52
+ maybeScrollToBottom();
53
+ }) });
54
+ }
55
+
30
56
  function createThinkingElement() {
31
57
  var el = document.createElement("div");
32
58
  el.className = "thinking-item";
@@ -111,14 +137,13 @@ export function appendThinkingText(text) {
111
137
  detailsText: thinkingState.detailsText + separator + text,
112
138
  latestText: segmentText.trim() ? segmentText : thinkingState.latestText,
113
139
  });
114
- if (!store.get('replayingHistory')) keepActiveThinkingAtTail();
115
- updateThinkingView(thinkingState.el, thinkingState.segmentText || thinkingState.latestText, thinkingState.detailsText);
116
- maybeScrollToBottom();
140
+ scheduleThinkingRender();
117
141
  }
118
142
 
119
143
  export function stopThinkingSegment(duration) {
120
144
  var thinkingState = getThinkingState();
121
145
  if (!thinkingState || !thinkingState.active) return;
146
+ cancelThinkingRender();
122
147
  var seconds = typeof duration === "number" ? duration : (Date.now() - thinkingState.startTime) / 1000;
123
148
  thinkingState = updateThinkingState(thinkingState, {
124
149
  totalDuration: thinkingState.totalDuration + seconds,
@@ -145,20 +170,25 @@ export function resetThinkingTurn() {
145
170
  }
146
171
 
147
172
  export function clearThinkingState() {
173
+ cancelThinkingRender();
148
174
  setThinkingState(null);
149
175
  }
150
176
 
151
177
  export function saveThinkingState() {
178
+ flushThinkingRender();
152
179
  return getThinkingState();
153
180
  }
154
181
 
155
182
  export function restoreThinkingState(saved) {
183
+ cancelThinkingRender();
156
184
  setThinkingState(saved || null);
185
+ if (saved && saved.active) scheduleThinkingRender();
157
186
  }
158
187
 
159
188
  export function resumeThinkingAfterReplay(processing) {
160
189
  var thinkingState = getThinkingState();
161
190
  if (!thinkingState || !thinkingState.active || !processing) return;
191
+ flushThinkingRender();
162
192
  keepActiveThinkingAtTail();
163
193
  setThinkingViewLive(thinkingState.el, !isMateThinking());
164
194
  showMateActivity(thinkingState.el, true, !!thinkingState.detailsText.trim());
@@ -1,7 +1,26 @@
1
1
  import { thinkingSummary } from './thinking-summary.js';
2
2
  import { renderMarkdown } from './markdown.js';
3
+ import { store } from './store.js';
4
+
5
+ // Retain complete source for each historical entry; parse only on disclosure.
6
+ function thinkingDetailsCache() {
7
+ var cache = store.get('thinkingDetailsCache');
8
+ if (!cache) {
9
+ cache = new WeakMap();
10
+ store.set({ thinkingDetailsCache: cache });
11
+ }
12
+ return cache;
13
+ }
14
+
15
+ function renderThinkingDetails(el) {
16
+ var details = thinkingDetailsCache().get(el);
17
+ if (!details || details.rendered === details.text) return;
18
+ el.querySelector(".thinking-content").innerHTML = details.text.trim() ? renderMarkdown(details.text) : "";
19
+ details.rendered = details.text;
20
+ }
3
21
 
4
22
  export function prepareThinkingView(el) {
23
+ thinkingDetailsCache().set(el, { text: "", rendered: "" });
5
24
  el.hidden = false;
6
25
  el.classList.remove("empty", "done", "expanded", "thinking-live");
7
26
  var header = el.querySelector(".thinking-header");
@@ -21,6 +40,7 @@ export function bindThinkingView(el) {
21
40
  var header = el.querySelector(".thinking-header");
22
41
  function toggle() {
23
42
  if (header.getAttribute("aria-disabled") === "true") return;
43
+ if (!el.classList.contains("expanded")) renderThinkingDetails(el);
24
44
  el.classList.toggle("expanded");
25
45
  header.setAttribute("aria-expanded", el.classList.contains("expanded") ? "true" : "false");
26
46
  }
@@ -37,7 +57,9 @@ export function updateThinkingView(el, summaryText, detailsText) {
37
57
  var label = el.querySelector(".thinking-label");
38
58
  label.textContent = summary;
39
59
  label.title = summary;
40
- el.querySelector(".thinking-content").innerHTML = detailsText.trim() ? renderMarkdown(detailsText) : "";
60
+ var details = thinkingDetailsCache().get(el);
61
+ if (details) details.text = detailsText;
62
+ if (el.classList.contains("expanded")) renderThinkingDetails(el);
41
63
  }
42
64
 
43
65
  export function setThinkingViewLive(el, live) {
@@ -1,3 +1,5 @@
1
+ import { clayToolGroupSummary } from './clay-tool-presentation.js';
2
+ import { applyClayToolItem, applyClayToolResult, finishClayToolItem, findEndedClayTool, clayToolResultGroup, bindToolGroupDisclosure, syncToolGroupDisclosure, syncClayToolGroup } from './clay-tool-activity.js';
1
3
  import { escapeHtml, copyToClipboard } from './utils.js';
2
4
  import { iconHtml, refreshIcons } from './icons.js';
3
5
  import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
@@ -76,6 +78,8 @@ function findToolGroup(groupId) {
76
78
  }
77
79
 
78
80
  function toolGroupSummary(group) {
81
+ var claySummary = clayToolGroupSummary(group, tools);
82
+ if (claySummary) return claySummary;
79
83
  var names = group.toolNames;
80
84
  var count = names.length;
81
85
  var allDone = group.doneCount >= count;
@@ -167,10 +171,13 @@ function updateToolGroupHeader(group) {
167
171
  header.style.display = "none";
168
172
  group.el.classList.remove("collapsed");
169
173
  }
174
+ syncToolGroupDisclosure(group.el);
175
+ syncClayToolGroup(group);
170
176
  }
171
177
 
172
178
  function isPlanFile(filePath) {
173
- return filePath && filePath.indexOf(".claude/plans/") !== -1;
179
+ var info = (store.get('vendorInfo') || {})[store.get('currentVendor')] || {};
180
+ return !!filePath && (info.planPathSegments || []).some(function (segment) { return filePath.indexOf(segment) !== -1; });
174
181
  }
175
182
 
176
183
  export function toolSummary(name, input) {
@@ -1628,9 +1635,9 @@ export function createToolItem(id, name) {
1628
1635
  }
1629
1636
 
1630
1637
  applyProjectAssistantIdentity(groupEl, identity);
1631
- groupEl.querySelector(".tool-group-header").addEventListener("click", function () {
1632
- groupEl.classList.toggle("collapsed");
1633
- if (currentToolGroup) currentToolGroup.userToggled = true;
1638
+ bindToolGroupDisclosure(groupEl, function () {
1639
+ var group = findToolGroup(groupEl.dataset.groupId);
1640
+ if (group) group.userToggled = true;
1634
1641
  });
1635
1642
 
1636
1643
  ctx.addToMessages(groupEl);
@@ -1640,6 +1647,8 @@ export function createToolItem(id, name) {
1640
1647
  el: groupEl,
1641
1648
  id: "g" + toolGroupCounter,
1642
1649
  toolNames: [],
1650
+ toolIds: [],
1651
+ toolRecords: {},
1643
1652
  toolCount: 0,
1644
1653
  doneCount: 0,
1645
1654
  errorCount: 0,
@@ -1666,17 +1675,21 @@ export function createToolItem(id, name) {
1666
1675
 
1667
1676
  el.querySelector(".tool-name").textContent = name;
1668
1677
 
1678
+ tools[id] = { el: el, name: name, input: null, done: false, groupId: currentToolGroup.id };
1679
+ applyClayToolItem(tools[id]);
1680
+
1669
1681
  // Append to group instead of messages directly
1670
1682
  currentToolGroup.el.querySelector(".tool-group-items").appendChild(el);
1671
1683
  currentToolGroup.toolNames.push(name);
1684
+ currentToolGroup.toolIds.push(id);
1685
+ currentToolGroup.toolRecords[id] = tools[id];
1672
1686
  currentToolGroup.toolCount++;
1673
1687
  updateToolGroupHeader(currentToolGroup);
1674
1688
 
1675
1689
  refreshIcons();
1676
1690
  maybeScrollToBottom();
1677
1691
 
1678
- tools[id] = { el: el, name: name, input: null, done: false, groupId: currentToolGroup.id };
1679
- ctx.setActivity("Running " + name + "...");
1692
+ ctx.setActivity(tools[id].clayActivity ? tools[id].clayActivity.label : "Running " + name + "...");
1680
1693
  }
1681
1694
 
1682
1695
  export function updateToolExecuting(id, name, input) {
@@ -1684,6 +1697,12 @@ export function updateToolExecuting(id, name, input) {
1684
1697
  if (!tool) return;
1685
1698
 
1686
1699
  tool.input = input;
1700
+ if (applyClayToolItem(tool)) {
1701
+ ctx.setActivity(tool.clayActivity.label);
1702
+ updateToolGroupHeader(findToolGroup(tool.groupId));
1703
+ maybeScrollToBottom();
1704
+ return;
1705
+ }
1687
1706
  var descEl = tool.el.querySelector(".tool-desc");
1688
1707
  descEl.textContent = toolSummary(name, input);
1689
1708
 
@@ -1893,9 +1912,17 @@ function isImagePath(filePath) {
1893
1912
  }
1894
1913
 
1895
1914
  export function updateToolResult(id, content, isError, images) {
1896
- var tool = tools[id];
1915
+ var tool = tools[id] || findEndedClayTool(ctx.messagesEl, id);
1897
1916
  if (!tool) return;
1898
1917
 
1918
+ if (applyClayToolResult(tool, content, isError)) {
1919
+ markToolDone(id, tool.clayActivity.phase === "error" || tool.clayActivity.phase === "cancelled");
1920
+ applyClayToolItem(tool);
1921
+ updateToolGroupHeader(clayToolResultGroup(tool, findToolGroup(tool.groupId)));
1922
+ maybeScrollToBottom();
1923
+ return;
1924
+ }
1925
+
1899
1926
  // Re-render the sublabel from the now-complete input (streaming can have
1900
1927
  // built it from a partial one), but in its terminal form: a result has
1901
1928
  // arrived, so this call is no longer running.
@@ -2026,6 +2053,7 @@ export function markToolDone(id, isError) {
2026
2053
  icon.innerHTML = '<span class="check">' + iconHtml("check") + '</span>';
2027
2054
  }
2028
2055
  refreshIcons();
2056
+ finishClayToolItem(tool, findToolGroup(tool.groupId));
2029
2057
 
2030
2058
  // Update group state
2031
2059
  if (tool.groupId) {
@@ -2244,6 +2272,7 @@ export function removeToolFromGroup(toolId) {
2244
2272
  var group = findToolGroup(tool.groupId);
2245
2273
  if (!group) return;
2246
2274
  group.toolCount--;
2275
+ if (group.toolIds) group.toolIds = group.toolIds.filter(function (id) { return id !== toolId; });
2247
2276
  // Remove tool name from the names array (remove first occurrence)
2248
2277
  var idx = group.toolNames.indexOf(tool.name);
2249
2278
  if (idx !== -1) group.toolNames.splice(idx, 1);
@@ -635,10 +635,9 @@ export function attachTuiGrab(xterm, containerEl, localId, opts) {
635
635
  if (opts && opts.vendor) {
636
636
  var vendors = store.get('vendorInfo') || {};
637
637
  var info = vendors[opts.vendor];
638
- var legacyTuiVendors = ["claude"];
639
638
  var supportsTui = info
640
639
  ? Array.isArray(info.sessionModes) && info.sessionModes.indexOf("tui") !== -1
641
- : legacyTuiVendors.indexOf(opts.vendor) !== -1;
640
+ : false;
642
641
  if (!supportsTui) return;
643
642
  }
644
643
 
@@ -31,7 +31,11 @@ function addTerminalTypography(section) {
31
31
  var option = document.createElement('option'); option.value = font.family; option.textContent = font.label; family.appendChild(option);
32
32
  });
33
33
  function sync() {
34
- if (store.get('userTerminalDirty')) return;
34
+ if (store.get('userTerminalDirty')) {
35
+ var dirty = family.value !== getTerminalFontFamily() || size.value !== String(getTerminalFontSize());
36
+ store.set({userTerminalDirty:dirty});
37
+ if (dirty) return;
38
+ }
35
39
  var value = getTerminalFontFamily();
36
40
  if (!Array.from(family.options).some(function (option) { return option.value === value; })) {
37
41
  var option = document.createElement('option'); option.value = value; option.textContent = 'Custom font'; family.appendChild(option);
@@ -41,7 +45,10 @@ function addTerminalTypography(section) {
41
45
  family.dispatchEvent(new Event('settings-sync'));
42
46
  }
43
47
  async function save() {
44
- var value = Number(size.value); store.set({ userTerminalDirty: true });
48
+ var dirty = family.value !== getTerminalFontFamily() || size.value !== String(getTerminalFontSize());
49
+ store.set({userTerminalDirty:dirty});
50
+ if (!dirty) {retry.hidden = true;status.textContent = '';status.dataset.state = '';return;}
51
+ var value = Number(size.value);
45
52
  if (!Number.isInteger(value) || value < 9 || value > 32) { status.textContent = 'Choose a whole size from 9 to 32.'; status.dataset.state = 'error'; return; }
46
53
  family.disabled = true; size.disabled = true; retry.hidden = true; status.textContent = 'Saving…'; status.dataset.state = '';
47
54
  try {
@@ -50,6 +57,6 @@ function addTerminalTypography(section) {
50
57
  finally { family.disabled = false; size.disabled = false; family.dispatchEvent(new Event('settings-sync')); }
51
58
  }
52
59
  family.onchange = save; size.onchange = save; retry.onclick = save;
53
- size.oninput = function () { store.set({ userTerminalDirty: true }); };
60
+ size.oninput = function () { store.set({ userTerminalDirty: family.value !== getTerminalFontFamily() || size.value !== String(getTerminalFontSize()) }); };
54
61
  onTerminalFontChange(sync); sync(); enhanceSettingsSelects(content);
55
62
  }
@@ -19,6 +19,6 @@ function wrapEditor(content, title, summary) {
19
19
  }
20
20
  export function initPersonalEditors() {
21
21
  var allow=document.getElementById('us-claude-allow-list');var permission=wrapEditor(allow.closest('.settings-card'),'Personal allow rules',function(){var count=(store.get('userAllowSaved') || []).length;return count+' personal rules. Managed defaults apply separately.';});
22
- permission.done.textContent='Cancel';permission.done.onclick=function(){function cancel(){allow.value=(store.get('userAllowSaved') || []).join('\n');store.set({userAllowDirty:false});permission.content.hidden=true;permission.state.hidden=false;permission.edit.hidden=false;permission.edit.focus();}if(store.get('userAllowDirty'))showConfirmDialog('Discard unsaved command rules?', 'Discard',cancel);else cancel();};
22
+ permission.done.textContent='Cancel';permission.done.onclick=function(){function cancel(){allow.value=(store.get('userAllowSaved') || []).join('\n');store.set({userAllowDirty:false,userAllowNeedsRetry:false});permission.content.hidden=true;permission.state.hidden=false;permission.edit.hidden=false;permission.edit.focus();}if(store.get('userAllowDirty'))showConfirmDialog('Discard unsaved command rules?', 'Discard',cancel);else cancel();};
23
23
  store.subscribe(function(state,previous){if(state.userAllowSaved!==previous.userAllowSaved){permission.state.textContent=(state.userAllowSaved || []).length+' personal rules. Managed defaults apply separately.';if(previous.userAllowDirty && !state.userAllowDirty){permission.content.hidden=true;permission.state.hidden=false;permission.edit.hidden=false;permission.edit.focus();}}});
24
24
  }
@@ -69,12 +69,21 @@ export function loadUserSettingsExtras(page) {
69
69
  if (page === 'us-ai') { mountDefaultAi(document.getElementById('us-default-ai')); requestDefaultAi(); var ws=getWs();if(ws && ws.readyState===1)ws.send(JSON.stringify({type:'get_claude_allow_list'})); }
70
70
  }
71
71
  export function finishUserSettingsExtras() {closeSpeechPicker();unmountDefaultAi(document.getElementById('us-default-ai'));}
72
- export function guardUserSettingsLeave(done) {
72
+ export function guardUserSettingsLeave(done, destination) {
73
+ var active = document.querySelector('#user-settings .us-section.active');
74
+ var page = active && active.dataset.section;
75
+ if (destination && resolveUserSettingsPage(destination) === page) {done();return;}
76
+ function leaving(id) {return !destination || page === id;}
73
77
  var pin = document.getElementById('us-pin-form');
74
78
  var pinDirty = pin && !pin.classList.contains('hidden') && Array.from(pin.querySelectorAll('input')).some(function (input) {return !!input.value;});
75
79
  var key = document.getElementById('us-linear-key');
76
80
  if(store.get('speechPopup'))return;
77
- var dirty = store.get('personalEmailDirty') || store.get('personalLinearDirty') || store.get('userVoiceDirty') || store.get('notificationDraft') || store.get('appearanceSaveError') || store.get('userProfileDirty') || store.get('userTerminalDirty') || pinDirty || key && key.value || store.get('defaultAiDraftDirty') || store.get('userAllowDirty');
81
+ var dirty = leaving('us-integrations') && (store.get('personalEmailDirty') || store.get('personalLinearDirty') || key && key.value) ||
82
+ leaving('us-chat') && store.get('userVoiceDirty') ||
83
+ leaving('us-notifications') && store.get('notificationDraft') ||
84
+ leaving('us-appearance') && (store.get('appearanceDraft') || store.get('userTerminalDirty')) ||
85
+ leaving('us-account') && (store.get('userProfileDirty') || pinDirty) ||
86
+ leaving('us-ai') && (store.get('defaultAiDraftDirty') || store.get('userAllowDirty'));
78
87
  if (!dirty) {done();return;}
79
88
  showConfirmDialog('Your unsaved changes will remain in this tab. Leave this editor and keep the draft?', 'Keep draft and leave', done);
80
89
  }
@@ -27,7 +27,10 @@ export function initPersonalPreferences() {
27
27
  catch(error){preferenceStatus('us-layout-switcher',error.message+' Try again.',true);}
28
28
  finally{controls.forEach(function(el){el.disabled=false;});}
29
29
  };});
30
- var textarea=document.getElementById('us-claude-allow-list');textarea.oninput=function(){store.set({userAllowDirty:true});};
30
+ var textarea=document.getElementById('us-claude-allow-list');textarea.oninput=function(){
31
+ var patterns=textarea.value.split('\n').map(function(line){return line.trim();}).filter(Boolean);
32
+ store.set({userAllowDirty:!!store.get('userAllowNeedsRetry') || JSON.stringify(patterns)!==JSON.stringify(store.get('userAllowSaved') || [])});
33
+ };
31
34
  document.getElementById('us-claude-allow-save').onclick=function(){
32
35
  var ws=getWs();if(!ws || ws.readyState!==1){preferenceStatus('us-claude-allow-list','Disconnected. Your draft is retained.',true);return;}
33
36
  this.disabled=true;preferenceStatus('us-claude-allow-list','Saving…');ws.send(JSON.stringify({type:'set_claude_user_allow_list',patterns:textarea.value.split('\n').map(function(line){return line.trim();}).filter(Boolean)}));
@@ -37,11 +40,12 @@ export function initPersonalPreferences() {
37
40
  export function handlePersonalSettingsMessage(msg) {
38
41
  if(msg.type==='claude_allow_list') {
39
42
  store.set({userAllowSaved:msg.user || []});
43
+ if(store.get('userAllowDirty'))document.getElementById('us-claude-allow-list').oninput();
40
44
  if(store.get('userAllowDirty'))return;
41
45
  }
42
46
  if(msg.type==='set_claude_user_allow_list_result') {
43
47
  document.getElementById('us-claude-allow-save').disabled=false;
44
- if(msg.ok)store.set({userAllowDirty:msg.effective===false,userAllowSaved:msg.patterns || []});
48
+ if(msg.ok)store.set({userAllowDirty:msg.effective===false,userAllowNeedsRetry:msg.effective===false,userAllowSaved:msg.patterns || []});
45
49
  var message=msg.ok ? (msg.effective===false ? 'Saved to your account, but not installed in Claude. Check filesystem access, then Save to retry.' : 'Saved. Applies on the next Claude invocation.') : msg.error || 'Save failed. Your draft is retained.';
46
50
  preferenceStatus('us-claude-allow-list',message,!msg.ok || msg.effective===false);
47
51
  }
@@ -13,10 +13,20 @@ export function initPersonalProfile() {
13
13
  name.after(form); var input = form.querySelector('input');
14
14
  function close() { form.hidden = true; name.hidden = false; edit.hidden = false; edit.focus(); }
15
15
  edit.onclick = function () {
16
- if (!store.get('userProfileDirty')) input.value = getProfile().name || name.textContent;
16
+ if (!store.get('userProfileDirty')) {
17
+ input.value = name.textContent || getProfile().name;
18
+ store.set({userProfileSavedName:input.value.trim()});
19
+ }
17
20
  form.hidden = false; name.hidden = true; edit.hidden = true; input.focus(); input.select();
18
21
  };
19
- input.oninput = function () { store.set({ userProfileDirty: true }); };
22
+ input.oninput = function () { store.set({ userProfileDirty: input.value.trim() !== store.get('userProfileSavedName') }); };
23
+ store.subscribe(function (state, previous) {
24
+ if (state.userSettingsProfile === previous.userSettingsProfile || !state.userSettingsProfile) return;
25
+ var data = state.userSettingsProfile;
26
+ var saved = data.name || data.displayName || data.username || 'Clay User';
27
+ if (!state.userProfileDirty) input.value = saved;
28
+ store.set({userProfileSavedName:saved.trim(), userProfileDirty:input.value.trim() !== saved.trim()});
29
+ });
20
30
  form.onsubmit = async function (event) {
21
31
  event.preventDefault(); var save = form.querySelector('[type=submit]'); var status = form.querySelector('[role=status]');
22
32
  if (!input.value.trim()) { status.textContent = 'Enter a display name.'; return; }
@@ -75,7 +75,7 @@ export function initUserSettings() {
75
75
  for (var i = 0; i < navItems.length; i++) {
76
76
  navItems[i].addEventListener('click', function () {
77
77
  var section = this.dataset.section;
78
- guardUserSettingsLeave(function () { switchSection(section); });
78
+ guardUserSettingsLeave(function () { switchSection(section); }, section);
79
79
  });
80
80
  }
81
81
 
@@ -83,7 +83,8 @@ export function initUserSettings() {
83
83
  var navDropdown = document.getElementById('user-settings-nav-dropdown');
84
84
  if (navDropdown) {
85
85
  navDropdown.addEventListener('change', function () {
86
- var value = this.value; guardUserSettingsLeave(function () {switchSection(value);});
86
+ var value = this.value; guardUserSettingsLeave(function () {switchSection(value);}, value);
87
+ this.value = settingsEl.querySelector('.us-section.active').dataset.section;
87
88
  });
88
89
  }
89
90
 
@@ -1,3 +1,4 @@
1
+ import { escapeHtml } from './utils.js';
1
2
  import { store } from './store.js';
2
3
  import { getWs } from './ws-ref.js';
3
4
  import { iconHtml, refreshIcons } from './icons.js';
@@ -33,7 +34,8 @@ export function openLoginBrowser(msg) {
33
34
  }
34
35
  closeLoginBrowser();
35
36
  var isMcp = msg.kind === 'mcp';
36
- var title = isMcp ? 'Sign in to ' + (msg.label || 'MCP server') : (msg.vendor === "codex" ? "Sign in to Codex" : "Sign in to Claude");
37
+ var provider = (store.get('vendorInfo') || {})[msg.vendor] || {};
38
+ var title = 'Sign in to ' + (isMcp ? msg.label || 'MCP server' : provider.displayName || msg.vendor || 'Provider');
37
39
  store.set({ vendorLoginBrowser: {
38
40
  browserId: msg.browserId, vendor: msg.vendor, kind: msg.kind, connectionId: msg.connectionId, slug: msg.slug || store.get('currentSlug'),
39
41
  previousFocus: document.activeElement, decoding: false, pointerY: null, pointerId: null, mouseDown: false, moveTimer: null, pendingMove: null, resizeObserver: null, requestedSize: null, phase: "loading",
@@ -43,7 +45,7 @@ export function openLoginBrowser(msg) {
43
45
  dialog.className = "login-browser";
44
46
  dialog.setAttribute("aria-labelledby", "login-browser-title");
45
47
  dialog.innerHTML = '<header class="login-browser-header">' +
46
- '<img class="login-browser-vendor" src="/' + (msg.vendor === "codex" ? 'codex-avatar.png' : 'claude-code-avatar.png') + '" alt="">' +
48
+ '<img class="login-browser-vendor" src="' + escapeHtml(provider.avatar || '') + '" alt="">' +
47
49
  '<div class="login-browser-heading"><h2 id="login-browser-title"></h2>' +
48
50
  '<span class="login-browser-origin">Opening sign-in page…</span></div>' +
49
51
  '<div class="login-browser-actions">' +
@@ -56,7 +58,7 @@ export function openLoginBrowser(msg) {
56
58
  '<div class="login-browser-loading"><span class="login-browser-spinner" aria-hidden="true"></span><strong>Opening your sign-in page</strong><span>This may take a moment.</span></div>' +
57
59
  '<textarea class="login-browser-keyboard" aria-label="Type into the remote sign-in page" autocomplete="off" autocapitalize="off" spellcheck="false"></textarea></div>' +
58
60
  '<footer class="login-browser-footer"><div class="login-browser-info"><div class="login-browser-status"><span class="login-browser-status-dot" aria-hidden="true"></span><p role="status">Preparing sign-in…</p></div>' +
59
- '<p class="login-browser-privacy">Browser cookies are discarded when this session ends. You stay signed in to ' + (msg.vendor === "codex" ? 'Codex' : 'Claude') + ' after this browser closes.</p></div>' +
61
+ '<p class="login-browser-privacy">Browser cookies are discarded when this session ends. You stay signed in to ' + escapeHtml(provider.displayName || msg.vendor || 'the provider') + ' after this browser closes.</p></div>' +
60
62
  '<button type="button" data-action="terminal" class="login-browser-terminal">' + iconHtml("terminal") + '<span>Use terminal sign-in</span>' + iconHtml("arrow-up-right") + '</button></footer>';
61
63
  dialog.querySelector("h2").textContent = title;
62
64
  document.body.appendChild(dialog);
@@ -31,7 +31,7 @@ function vendorDisplayName(vendor) {
31
31
  var vendors = store.get('vendorInfo') || {};
32
32
  var info = vendors[vendor];
33
33
  if (info && info.displayName) return info.displayName;
34
- return vendor === "codex" ? "Codex" : "Claude Code";
34
+ return vendor || "Provider";
35
35
  }
36
36
 
37
37
  function currentProjectSlug() {
@@ -170,7 +170,7 @@ export function requestVendorLogin(vendor, opts) {
170
170
  vendor: target,
171
171
  auto: !!options.auto,
172
172
  sessionId: options.sessionId || store.get('activeSessionId') || null,
173
- mode: options.mode || ((target === "claude" || target === "codex") ? "browser" : "terminal"),
173
+ mode: options.mode || ((((store.get("vendorInfo") || {})[target] || {}).browserLoginSupported === true) ? "browser" : "terminal"),
174
174
  }));
175
175
  showLoginStartedBanner(target);
176
176
  return true;
@@ -1,4 +1,18 @@
1
- export var VENDOR_ORDER = ["claude", "codex", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
1
+ export var VENDOR_ORDER = ["claude", "codex", "muse", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
2
+
3
+ // Availability comes from the caller's server catalog. Unknown providers keep
4
+ // their catalog order within each availability group.
5
+ export function sortVendorEntries(entries) {
6
+ return entries.map(function (entry, index) { return { entry: entry, index: index }; }).sort(function (a, b) {
7
+ var installed = Number(!!b.entry.installed) - Number(!!a.entry.installed);
8
+ if (installed) return installed;
9
+ var aRank = VENDOR_ORDER.indexOf(a.entry.id);
10
+ var bRank = VENDOR_ORDER.indexOf(b.entry.id);
11
+ if (aRank === -1) aRank = VENDOR_ORDER.length;
12
+ if (bRank === -1) bRank = VENDOR_ORDER.length;
13
+ return aRank - bRank || a.index - b.index;
14
+ }).map(function (item) { return item.entry; });
15
+ }
2
16
  export var EXPERIMENTAL_VENDORS = ["grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode"];
3
17
 
4
18
  export function isExperimentalVendor(vendor) {