clay-server 5.2.0-beta.3 → 5.2.0-beta.5

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 (339) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +19 -16
  3. package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
  4. package/lib/canonical-clay-record-authority.js +15 -0
  5. package/lib/canonical-clay-scheduling-authority.js +19 -0
  6. package/lib/claude-hook-installer.js +28 -0
  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-workspace-controls.js +83 -0
  23. package/lib/clay-workspace-input.js +17 -0
  24. package/lib/clay-workspace-receipts.js +26 -0
  25. package/lib/daemon-project-access.js +11 -0
  26. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  27. package/lib/daemon-project-lifecycle.js +262 -0
  28. package/lib/daemon-projects.js +1 -1
  29. package/lib/daemon.js +73 -221
  30. package/lib/issues-service.js +29 -15
  31. package/lib/knowledge-db-import.js +5 -4
  32. package/lib/knowledge-db-service.js +9 -6
  33. package/lib/knowledge-document-index.js +2 -1
  34. package/lib/knowledge-file-store.js +7 -1
  35. package/lib/knowledge-product-operations.js +46 -0
  36. package/lib/mate-avatar-catalog.js +12 -0
  37. package/lib/mate-avatar-migration.js +41 -0
  38. package/lib/mate-creation-mcp-server.js +2 -0
  39. package/lib/mate-knowledge-canonical-access.js +10 -0
  40. package/lib/mate-knowledge-service.js +7 -4
  41. package/lib/mate-lifecycle-service.js +94 -0
  42. package/lib/mate-message-delivery.js +43 -0
  43. package/lib/mate-ready-creation.js +9 -1
  44. package/lib/mate-schedule-input.js +29 -0
  45. package/lib/mate-schedule-lifecycle.js +151 -0
  46. package/lib/mate-scheduling.js +103 -0
  47. package/lib/mates.js +18 -6
  48. package/lib/mcp-agent-tools.js +117 -0
  49. package/lib/mcp-connection-store.js +119 -15
  50. package/lib/mcp-import-credentials.js +31 -0
  51. package/lib/mcp-local-transport.js +13 -0
  52. package/lib/mcp-oauth.js +8 -6
  53. package/lib/mcp-query-bridge.js +30 -0
  54. package/lib/mcp-remote-connections.js +74 -20
  55. package/lib/mcp-source-adapters.js +91 -0
  56. package/lib/mcp-source-context.js +42 -0
  57. package/lib/mcp-source-discovery.js +169 -0
  58. package/lib/mcp-source-plugins.js +68 -0
  59. package/lib/mcp-source-restrictions.js +47 -0
  60. package/lib/mcp-tool-policy.js +34 -0
  61. package/lib/notes-checklist.js +50 -0
  62. package/lib/notes.js +49 -3
  63. package/lib/os-users.js +20 -5
  64. package/lib/project-admin-overview.js +51 -0
  65. package/lib/project-ask-user.js +8 -4
  66. package/lib/project-debate.js +1 -0
  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-knowledge.js +32 -18
  73. package/lib/project-lifecycle-operations.js +98 -0
  74. package/lib/project-lifecycle-process-witness.js +32 -0
  75. package/lib/project-lifecycle-runtime.js +50 -0
  76. package/lib/project-linux-access.js +22 -0
  77. package/lib/project-logs-canonical-clay.js +27 -0
  78. package/lib/project-logs-clay-query.js +16 -0
  79. package/lib/project-logs-schema.js +4 -2
  80. package/lib/project-logs-service.js +8 -7
  81. package/lib/project-logs.js +1 -0
  82. package/lib/project-loop.js +1 -0
  83. package/lib/project-mate-chat-control.js +55 -0
  84. package/lib/project-mate-creation-proposal.js +18 -3
  85. package/lib/project-mate-interaction.js +8 -0
  86. package/lib/project-mcp.js +54 -16
  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-pending-message-queue.js +1 -0
  92. package/lib/project-product-control.js +87 -0
  93. package/lib/project-product-session-queue.js +46 -0
  94. package/lib/project-push-access.js +28 -0
  95. package/lib/project-scheduled-message-queue.js +30 -0
  96. package/lib/project-scheduled-messages.js +63 -64
  97. package/lib/project-scheduled-task-control.js +83 -0
  98. package/lib/project-scheduled-tasks.js +13 -2
  99. package/lib/project-scheduling-access.js +10 -0
  100. package/lib/project-session-actions.js +65 -0
  101. package/lib/project-session-delete.js +8 -1
  102. package/lib/project-session-document.js +8 -3
  103. package/lib/project-session-github.js +63 -19
  104. package/lib/project-session-history-actions.js +165 -0
  105. package/lib/project-session-linear.js +14 -6
  106. package/lib/project-session-notes.js +7 -3
  107. package/lib/project-session-runtime-actions.js +63 -0
  108. package/lib/project-session-vendor.js +54 -0
  109. package/lib/project-sessions.js +68 -241
  110. package/lib/project-shared-browser.js +56 -12
  111. package/lib/project-user-message.js +30 -1
  112. package/lib/project-workspace-operations.js +126 -0
  113. package/lib/project-workspace-query.js +9 -2
  114. package/lib/project.js +144 -37
  115. package/lib/public/app.js +5 -0
  116. package/lib/public/appearance-bootstrap.js +14 -0
  117. package/lib/public/css/clay-tool-activity.css +51 -0
  118. package/lib/public/css/clay-workspace.css +29 -0
  119. package/lib/public/css/home-debate-planning.css +2 -0
  120. package/lib/public/css/home-mate-creation.css +5 -0
  121. package/lib/public/css/mate-avatar-gallery.css +12 -0
  122. package/lib/public/css/mate-conversation.css +10 -5
  123. package/lib/public/css/overlays.css +4 -2
  124. package/lib/public/css/project-admin-overview.css +13 -0
  125. package/lib/public/css/session-folders.css +11 -0
  126. package/lib/public/css/session-github.css +46 -1
  127. package/lib/public/css/shared-browser.css +35 -1
  128. package/lib/public/css/sticky-notes.css +61 -1
  129. package/lib/public/index.html +11 -5
  130. package/lib/public/mate-avatars/B01.png +0 -0
  131. package/lib/public/mate-avatars/B02.png +0 -0
  132. package/lib/public/mate-avatars/B03.png +0 -0
  133. package/lib/public/mate-avatars/B04.png +0 -0
  134. package/lib/public/mate-avatars/B05.png +0 -0
  135. package/lib/public/mate-avatars/B06.png +0 -0
  136. package/lib/public/mate-avatars/B07.png +0 -0
  137. package/lib/public/mate-avatars/B08.png +0 -0
  138. package/lib/public/mate-avatars/B09.png +0 -0
  139. package/lib/public/mate-avatars/B10.png +0 -0
  140. package/lib/public/mate-avatars/B11.png +0 -0
  141. package/lib/public/mate-avatars/B12.png +0 -0
  142. package/lib/public/mate-avatars/B13.png +0 -0
  143. package/lib/public/mate-avatars/B14.png +0 -0
  144. package/lib/public/mate-avatars/B15.png +0 -0
  145. package/lib/public/mate-avatars/B16.png +0 -0
  146. package/lib/public/mate-avatars/B17.png +0 -0
  147. package/lib/public/mate-avatars/B18.png +0 -0
  148. package/lib/public/mate-avatars/B19.png +0 -0
  149. package/lib/public/mate-avatars/B20.png +0 -0
  150. package/lib/public/mate-avatars/B21.png +0 -0
  151. package/lib/public/mate-avatars/B22.png +0 -0
  152. package/lib/public/mate-avatars/B23.png +0 -0
  153. package/lib/public/mate-avatars/B24.png +0 -0
  154. package/lib/public/mate-avatars/B25.png +0 -0
  155. package/lib/public/mate-avatars/B26.png +0 -0
  156. package/lib/public/mate-avatars/B27.png +0 -0
  157. package/lib/public/mate-avatars/B28.png +0 -0
  158. package/lib/public/mate-avatars/B29.png +0 -0
  159. package/lib/public/mate-avatars/B30.png +0 -0
  160. package/lib/public/mate-avatars/B31.png +0 -0
  161. package/lib/public/mate-avatars/B32.png +0 -0
  162. package/lib/public/mate-avatars/B33.png +0 -0
  163. package/lib/public/mate-avatars/B34.png +0 -0
  164. package/lib/public/mate-avatars/B35.png +0 -0
  165. package/lib/public/mate-avatars/B36.png +0 -0
  166. package/lib/public/mate-avatars/B37.png +0 -0
  167. package/lib/public/mate-avatars/B38.png +0 -0
  168. package/lib/public/mate-avatars/B39.png +0 -0
  169. package/lib/public/mate-avatars/B40.png +0 -0
  170. package/lib/public/mate-avatars/B41.png +0 -0
  171. package/lib/public/mate-avatars/B42.png +0 -0
  172. package/lib/public/mate-avatars/B43.png +0 -0
  173. package/lib/public/mate-avatars/B44.png +0 -0
  174. package/lib/public/mate-avatars/B45.png +0 -0
  175. package/lib/public/mate-avatars/B46.png +0 -0
  176. package/lib/public/mate-avatars/B47.png +0 -0
  177. package/lib/public/mate-avatars/B48.png +0 -0
  178. package/lib/public/mate-avatars/C01.png +0 -0
  179. package/lib/public/mate-avatars/C02.png +0 -0
  180. package/lib/public/mate-avatars/C03.png +0 -0
  181. package/lib/public/mate-avatars/C04.png +0 -0
  182. package/lib/public/mate-avatars/C05.png +0 -0
  183. package/lib/public/mate-avatars/C06.png +0 -0
  184. package/lib/public/mate-avatars/C07.png +0 -0
  185. package/lib/public/mate-avatars/C08.png +0 -0
  186. package/lib/public/mate-avatars/C09.png +0 -0
  187. package/lib/public/mate-avatars/C10.png +0 -0
  188. package/lib/public/mate-avatars/C11.png +0 -0
  189. package/lib/public/mate-avatars/C12.png +0 -0
  190. package/lib/public/mate-avatars/C13.png +0 -0
  191. package/lib/public/mate-avatars/C14.png +0 -0
  192. package/lib/public/mate-avatars/C15.png +0 -0
  193. package/lib/public/mate-avatars/C16.png +0 -0
  194. package/lib/public/mate-avatars/C17.png +0 -0
  195. package/lib/public/mate-avatars/C18.png +0 -0
  196. package/lib/public/mate-avatars/C19.png +0 -0
  197. package/lib/public/mate-avatars/C20.png +0 -0
  198. package/lib/public/mate-avatars/C21.png +0 -0
  199. package/lib/public/mate-avatars/C22.png +0 -0
  200. package/lib/public/mate-avatars/C23.png +0 -0
  201. package/lib/public/mate-avatars/C24.png +0 -0
  202. package/lib/public/mate-avatars/C25.png +0 -0
  203. package/lib/public/mate-avatars/C26.png +0 -0
  204. package/lib/public/mate-avatars/C27.png +0 -0
  205. package/lib/public/mate-avatars/C28.png +0 -0
  206. package/lib/public/mate-avatars/C29.png +0 -0
  207. package/lib/public/mate-avatars/C30.png +0 -0
  208. package/lib/public/mate-avatars/C31.png +0 -0
  209. package/lib/public/mate-avatars/C32.png +0 -0
  210. package/lib/public/mate-avatars/C33.png +0 -0
  211. package/lib/public/mate-avatars/C34.png +0 -0
  212. package/lib/public/mate-avatars/C35.png +0 -0
  213. package/lib/public/mate-avatars/C36.png +0 -0
  214. package/lib/public/mate-avatars/C37.png +0 -0
  215. package/lib/public/mate-avatars/C38.png +0 -0
  216. package/lib/public/mate-avatars/C39.png +0 -0
  217. package/lib/public/mate-avatars/C40.png +0 -0
  218. package/lib/public/mate-avatars/C41.png +0 -0
  219. package/lib/public/mate-avatars/C42.png +0 -0
  220. package/lib/public/mate-avatars/C43.png +0 -0
  221. package/lib/public/mate-avatars/C44.png +0 -0
  222. package/lib/public/mate-avatars/C45.png +0 -0
  223. package/lib/public/mate-avatars/C46.png +0 -0
  224. package/lib/public/mate-avatars/C47.png +0 -0
  225. package/lib/public/mate-avatars/C48.png +0 -0
  226. package/lib/public/mate-avatars/catalog.json +482 -0
  227. package/lib/public/mate-avatars/lead-clay.png +0 -0
  228. package/lib/public/modules/admin-settings-projects.js +6 -2
  229. package/lib/public/modules/app-connection.js +8 -1
  230. package/lib/public/modules/app-message-router.js +4 -0
  231. package/lib/public/modules/app-messages.js +31 -5
  232. package/lib/public/modules/app-projects.js +5 -0
  233. package/lib/public/modules/app-rendering.js +1 -1
  234. package/lib/public/modules/avatar.js +15 -41
  235. package/lib/public/modules/chat-render-runtime.js +10 -1
  236. package/lib/public/modules/clay-browser-links.js +47 -0
  237. package/lib/public/modules/clay-log-links.js +1 -1
  238. package/lib/public/modules/clay-owner-navigation.js +27 -0
  239. package/lib/public/modules/clay-tool-activity.js +140 -0
  240. package/lib/public/modules/clay-tool-presentation.js +338 -0
  241. package/lib/public/modules/command-palette.js +2 -2
  242. package/lib/public/modules/debate.js +1 -0
  243. package/lib/public/modules/default-ai.js +11 -3
  244. package/lib/public/modules/history-replay-batch.js +5 -1
  245. package/lib/public/modules/home-debate-live.js +2 -0
  246. package/lib/public/modules/home-debate-planning.js +7 -7
  247. package/lib/public/modules/home-mate-avatar-editor.js +127 -208
  248. package/lib/public/modules/home-mate-chat.js +8 -6
  249. package/lib/public/modules/home-mate-creation.js +73 -1
  250. package/lib/public/modules/home-mate-settings.js +19 -12
  251. package/lib/public/modules/markdown.js +4 -0
  252. package/lib/public/modules/mate-avatar-catalog.js +97 -0
  253. package/lib/public/modules/mate-management.js +40 -7
  254. package/lib/public/modules/mcp-connection-view.js +31 -13
  255. package/lib/public/modules/mcp-discovery-view.js +31 -0
  256. package/lib/public/modules/mcp-ui.js +23 -4
  257. package/lib/public/modules/mention.js +3 -0
  258. package/lib/public/modules/pane-browser-bridge.js +20 -0
  259. package/lib/public/modules/personal-settings-dialog.js +7 -1
  260. package/lib/public/modules/profile.js +1 -238
  261. package/lib/public/modules/project-access-recovery.js +17 -0
  262. package/lib/public/modules/project-admin-overview.js +21 -0
  263. package/lib/public/modules/project-mate-navigation.js +99 -14
  264. package/lib/public/modules/search-clay-chat.js +16 -2
  265. package/lib/public/modules/session-folder-layout.js +1 -1
  266. package/lib/public/modules/session-folder-view-menu.js +9 -0
  267. package/lib/public/modules/session-github.js +85 -14
  268. package/lib/public/modules/session-work-primary.js +51 -0
  269. package/lib/public/modules/shared-browser-settings.js +112 -0
  270. package/lib/public/modules/shared-browser-tabs.js +13 -3
  271. package/lib/public/modules/shared-browser.js +27 -6
  272. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  273. package/lib/public/modules/split-view.js +2 -0
  274. package/lib/public/modules/sticky-note-markdown.js +72 -6
  275. package/lib/public/modules/sticky-notes-actions.js +30 -0
  276. package/lib/public/modules/sticky-notes-browser.js +9 -2
  277. package/lib/public/modules/sticky-notes-card.js +23 -7
  278. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  279. package/lib/public/modules/sticky-notes-editor.js +108 -44
  280. package/lib/public/modules/sticky-notes-shared.js +50 -2
  281. package/lib/public/modules/sticky-notes-state.js +66 -0
  282. package/lib/public/modules/sticky-notes.js +34 -51
  283. package/lib/public/modules/theme.js +8 -1
  284. package/lib/public/modules/thinking-lifecycle.js +33 -3
  285. package/lib/public/modules/thinking-view.js +23 -1
  286. package/lib/public/modules/tools.js +36 -7
  287. package/lib/public/modules/user-settings-appearance.js +10 -3
  288. package/lib/public/modules/user-settings-editors.js +1 -1
  289. package/lib/public/modules/user-settings-layout.js +11 -2
  290. package/lib/public/modules/user-settings-preferences.js +6 -2
  291. package/lib/public/modules/user-settings-profile.js +12 -2
  292. package/lib/public/modules/user-settings.js +3 -2
  293. package/lib/public/style.css +3 -0
  294. package/lib/public/sw.js +3 -1
  295. package/lib/scheduled-task-records.js +3 -1
  296. package/lib/scheduler.js +2 -0
  297. package/lib/sdk-bridge.js +228 -23
  298. package/lib/sdk-message-processor.js +2 -0
  299. package/lib/sdk-response-recovery.js +155 -0
  300. package/lib/server-admin.js +63 -16
  301. package/lib/server-appearance-settings.js +13 -1
  302. package/lib/server-dm.js +2 -1
  303. package/lib/server-global-ws.js +10 -20
  304. package/lib/server-home-chat-events.js +18 -4
  305. package/lib/server-home-chat.js +12 -16
  306. package/lib/server-home-mate-creation.js +1 -0
  307. package/lib/server-home-mcp-consent.js +14 -0
  308. package/lib/server-mates.js +25 -62
  309. package/lib/server-owner-ui.js +77 -0
  310. package/lib/server-path-access.js +38 -0
  311. package/lib/server-project-removal.js +22 -0
  312. package/lib/server-settings.js +4 -1
  313. package/lib/server.js +144 -41
  314. package/lib/session-config-transition.js +45 -0
  315. package/lib/session-folders.js +7 -2
  316. package/lib/session-github.js +9 -2
  317. package/lib/session-visibility.js +5 -2
  318. package/lib/session-work-primary.js +50 -0
  319. package/lib/sessions.js +13 -0
  320. package/lib/shared-browser-cookie-sync.js +32 -0
  321. package/lib/shared-browser-cookies.js +79 -0
  322. package/lib/shared-browser-runtime.js +25 -9
  323. package/lib/shared-browser-storage-sync.js +43 -0
  324. package/lib/shared-browser-storage.js +101 -0
  325. package/lib/shared-browser-tools.js +2 -1
  326. package/lib/shared-browser-worker.js +46 -10
  327. package/lib/terminal-manager.js +52 -0
  328. package/lib/users-permissions.js +2 -8
  329. package/lib/users.js +4 -0
  330. package/lib/workspace-query-access.js +4 -1
  331. package/lib/ws-schema.js +8 -2
  332. package/lib/yoke/acp-agent-profiles.js +2 -2
  333. package/lib/yoke/adapters/claude.js +47 -8
  334. package/lib/yoke/adapters/codex.js +52 -111
  335. package/lib/yoke/claude-worker-startup.js +28 -0
  336. package/lib/yoke/codex-idle-recovery.js +81 -1
  337. package/lib/yoke/codex-recovery-admission.js +164 -0
  338. package/lib/yoke/session-tool-transport.js +3 -0
  339. package/package.json +5 -1
@@ -0,0 +1,66 @@
1
+ // Reconcile canonical broadcasts with optimistic checklist actions and text drafts.
2
+ import { store } from './store.js';
3
+ import { getNoteUiState, setNoteUiState, send } from './sticky-notes-shared.js';
4
+
5
+ function project(ui, pending, error) {
6
+ var canonical = ui.canonical || {};
7
+ var text = ui.draft ? ui.text : canonical.text || "";
8
+ var mode = canonical.checklistMode === true;
9
+ var state = Object.assign({}, canonical.checklistState || {});
10
+ pending.forEach(function (request) {
11
+ var changes = request.changes;
12
+ if (changes.text !== undefined) text = changes.text;
13
+ if (changes.checklistMode !== undefined) mode = changes.checklistMode;
14
+ if (changes.checklistKey) state[decodeURIComponent(changes.checklistKey)] = changes.checklistChecked;
15
+ });
16
+ return setNoteUiState(canonical.id, {
17
+ text: text,
18
+ checklistMode: mode, checklistState: state, pending: pending, error: error || '',
19
+ });
20
+ }
21
+
22
+ export function reconcileNote(data) {
23
+ var ui = getNoteUiState(data.id, data);
24
+ var draft = ui.draft && ui.text !== (data.text || '');
25
+ ui = setNoteUiState(data.id, { canonical: data, canonicalRevision: (ui.canonicalRevision || 0) + 1, draft: draft });
26
+ project(ui, ui.pending || [], ui.error);
27
+ }
28
+
29
+ export function mutateChecklist(id, changes) {
30
+ var ui = getNoteUiState(id);
31
+ var requestId = 'note-update-' + Date.now() + '-' + Math.random().toString(36).slice(2);
32
+ var pending = (ui.pending || []).concat([{ requestId: requestId, changes: changes, revision: ui.canonicalRevision, draftText: ui.text }]);
33
+ project(ui, pending, '');
34
+ if (!send(Object.assign({}, changes, { type: changes.humanClose ? 'note_close' : 'note_update', id: id, requestId: requestId }))) {
35
+ project(getNoteUiState(id), pending.slice(0, -1), 'Connection unavailable');
36
+ }
37
+ }
38
+
39
+ export function reconcileNoteResult(msg) {
40
+ var ui = getNoteUiState(msg.noteId);
41
+ var request = (ui.pending || []).find(function (item) { return item.requestId === msg.requestId; });
42
+ if (!request) return;
43
+ // The server broadcasts before acknowledgement. Use the returned note when
44
+ // that broadcast was absent, but never replace a newer remote canonical note.
45
+ if (msg.ok && msg.note && ui.canonicalRevision === request.revision) {
46
+ reconcileNote(msg.note);
47
+ ui = getNoteUiState(msg.noteId);
48
+ }
49
+ if (msg.ok && request.changes.checklistConvert && ui.text === request.draftText) {
50
+ ui = setNoteUiState(msg.noteId, { draft: false, text: ui.canonical.text || '' });
51
+ }
52
+ if (msg.ok && (request.changes.checklistEdit || request.changes.checklistConvert || request.changes.humanClose)) {
53
+ ui = setNoteUiState(msg.noteId, { checklistInputs: null });
54
+ }
55
+ var remaining = ui.pending.filter(function (item) { return item.requestId !== msg.requestId; });
56
+ project(ui, remaining, msg.ok ? '' : msg.error || 'Could not save checklist change');
57
+ return ui.canonical;
58
+ }
59
+
60
+ store.subscribe(function (next, previous) {
61
+ if (next.connected || next.connected === previous.connected) return;
62
+ var all = next.stickyNoteUiState || {};
63
+ Object.keys(all).forEach(function (id) {
64
+ if (all[id].pending && all[id].pending.length) project(all[id], [], 'Connection unavailable');
65
+ });
66
+ });
@@ -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
+ });
@@ -614,7 +614,14 @@ function rebuildPicker() {
614
614
 
615
615
  // --- Init ---
616
616
  export function initTheme() {
617
- currentThemeId = getSystemMode() === 'light' ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID;
617
+ var seed = window.__clayAppearanceSeed;
618
+ if (seed && seed.configured) store.set({ appearancePreference: seed });
619
+ var initialMode = seed && seed.configured && (seed.mode === 'light' || seed.mode === 'dark')
620
+ ? seed.mode
621
+ : getSystemMode();
622
+ currentThemeId = seed && seed.configured && seed[initialMode + 'Theme']
623
+ ? seed[initialMode + 'Theme']
624
+ : (initialMode === 'light' ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID);
618
625
  document.body.classList.toggle('wide-view', isWideView());
619
626
 
620
627
  // Load all themes from server, then apply properly
@@ -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,6 +171,8 @@ 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) {
@@ -244,6 +250,7 @@ export function renderAskUserQuestion(toolId, input) {
244
250
 
245
251
  var container = document.createElement("div");
246
252
  container.className = "ask-user-container";
253
+ if (input.source === "mcp-import") container.classList.add("mcp-import-consent");
247
254
  container.dataset.toolId = toolId;
248
255
  var requestKey = getAskUserRequestKey(toolId);
249
256
  container.dataset.askUserRequestKey = requestKey;
@@ -364,7 +371,7 @@ export function renderAskUserQuestion(toolId, input) {
364
371
  }
365
372
  });
366
373
  otherDiv.appendChild(otherInput);
367
- qDiv.appendChild(otherDiv);
374
+ if (q.allowOther !== false || !(q.options || []).length) qDiv.appendChild(otherDiv);
368
375
  container.appendChild(qDiv);
369
376
  });
370
377
 
@@ -1627,9 +1634,9 @@ export function createToolItem(id, name) {
1627
1634
  }
1628
1635
 
1629
1636
  applyProjectAssistantIdentity(groupEl, identity);
1630
- groupEl.querySelector(".tool-group-header").addEventListener("click", function () {
1631
- groupEl.classList.toggle("collapsed");
1632
- if (currentToolGroup) currentToolGroup.userToggled = true;
1637
+ bindToolGroupDisclosure(groupEl, function () {
1638
+ var group = findToolGroup(groupEl.dataset.groupId);
1639
+ if (group) group.userToggled = true;
1633
1640
  });
1634
1641
 
1635
1642
  ctx.addToMessages(groupEl);
@@ -1639,6 +1646,8 @@ export function createToolItem(id, name) {
1639
1646
  el: groupEl,
1640
1647
  id: "g" + toolGroupCounter,
1641
1648
  toolNames: [],
1649
+ toolIds: [],
1650
+ toolRecords: {},
1642
1651
  toolCount: 0,
1643
1652
  doneCount: 0,
1644
1653
  errorCount: 0,
@@ -1665,17 +1674,21 @@ export function createToolItem(id, name) {
1665
1674
 
1666
1675
  el.querySelector(".tool-name").textContent = name;
1667
1676
 
1677
+ tools[id] = { el: el, name: name, input: null, done: false, groupId: currentToolGroup.id };
1678
+ applyClayToolItem(tools[id]);
1679
+
1668
1680
  // Append to group instead of messages directly
1669
1681
  currentToolGroup.el.querySelector(".tool-group-items").appendChild(el);
1670
1682
  currentToolGroup.toolNames.push(name);
1683
+ currentToolGroup.toolIds.push(id);
1684
+ currentToolGroup.toolRecords[id] = tools[id];
1671
1685
  currentToolGroup.toolCount++;
1672
1686
  updateToolGroupHeader(currentToolGroup);
1673
1687
 
1674
1688
  refreshIcons();
1675
1689
  maybeScrollToBottom();
1676
1690
 
1677
- tools[id] = { el: el, name: name, input: null, done: false, groupId: currentToolGroup.id };
1678
- ctx.setActivity("Running " + name + "...");
1691
+ ctx.setActivity(tools[id].clayActivity ? tools[id].clayActivity.label : "Running " + name + "...");
1679
1692
  }
1680
1693
 
1681
1694
  export function updateToolExecuting(id, name, input) {
@@ -1683,6 +1696,12 @@ export function updateToolExecuting(id, name, input) {
1683
1696
  if (!tool) return;
1684
1697
 
1685
1698
  tool.input = input;
1699
+ if (applyClayToolItem(tool)) {
1700
+ ctx.setActivity(tool.clayActivity.label);
1701
+ updateToolGroupHeader(findToolGroup(tool.groupId));
1702
+ maybeScrollToBottom();
1703
+ return;
1704
+ }
1686
1705
  var descEl = tool.el.querySelector(".tool-desc");
1687
1706
  descEl.textContent = toolSummary(name, input);
1688
1707
 
@@ -1892,9 +1911,17 @@ function isImagePath(filePath) {
1892
1911
  }
1893
1912
 
1894
1913
  export function updateToolResult(id, content, isError, images) {
1895
- var tool = tools[id];
1914
+ var tool = tools[id] || findEndedClayTool(ctx.messagesEl, id);
1896
1915
  if (!tool) return;
1897
1916
 
1917
+ if (applyClayToolResult(tool, content, isError)) {
1918
+ markToolDone(id, tool.clayActivity.phase === "error" || tool.clayActivity.phase === "cancelled");
1919
+ applyClayToolItem(tool);
1920
+ updateToolGroupHeader(clayToolResultGroup(tool, findToolGroup(tool.groupId)));
1921
+ maybeScrollToBottom();
1922
+ return;
1923
+ }
1924
+
1898
1925
  // Re-render the sublabel from the now-complete input (streaming can have
1899
1926
  // built it from a partial one), but in its terminal form: a result has
1900
1927
  // arrived, so this call is no longer running.
@@ -2025,6 +2052,7 @@ export function markToolDone(id, isError) {
2025
2052
  icon.innerHTML = '<span class="check">' + iconHtml("check") + '</span>';
2026
2053
  }
2027
2054
  refreshIcons();
2055
+ finishClayToolItem(tool, findToolGroup(tool.groupId));
2028
2056
 
2029
2057
  // Update group state
2030
2058
  if (tool.groupId) {
@@ -2243,6 +2271,7 @@ export function removeToolFromGroup(toolId) {
2243
2271
  var group = findToolGroup(tool.groupId);
2244
2272
  if (!group) return;
2245
2273
  group.toolCount--;
2274
+ if (group.toolIds) group.toolIds = group.toolIds.filter(function (id) { return id !== toolId; });
2246
2275
  // Remove tool name from the names array (remove first occurrence)
2247
2276
  var idx = group.toolNames.indexOf(tool.name);
2248
2277
  if (idx !== -1) group.toolNames.splice(idx, 1);
@@ -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