clay-server 5.2.0-beta.8 → 5.2.0

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 (143) hide show
  1. package/README.md +0 -4
  2. package/lib/clay-product-control-tools.js +1 -0
  3. package/lib/clay-product-control.js +14 -11
  4. package/lib/clay-vendor-usage.js +44 -0
  5. package/lib/github-star-count.js +147 -0
  6. package/lib/notes-checklist.js +43 -1
  7. package/lib/notes.js +2 -1
  8. package/lib/pane-pending-interactions.js +20 -0
  9. package/lib/pane-session-activation.js +114 -0
  10. package/lib/pane-session-transport.js +26 -0
  11. package/lib/project-session-notes.js +33 -8
  12. package/lib/project-sessions.js +78 -29
  13. package/lib/project-start-protocol.js +29 -0
  14. package/lib/project-vendor-login.js +2 -0
  15. package/lib/project.js +16 -4
  16. package/lib/public/app.js +41 -18
  17. package/lib/public/css/contextual-clay.css +2 -2
  18. package/lib/public/css/filebrowser.css +1 -1
  19. package/lib/public/css/input.css +6 -0
  20. package/lib/public/css/mate-loading-scene.css +21 -0
  21. package/lib/public/css/overlays.css +64 -256
  22. package/lib/public/css/project-mate-creation.css +5 -6
  23. package/lib/public/css/project-start.css +108 -0
  24. package/lib/public/css/projects-hub.css +53 -14
  25. package/lib/public/css/settings-shell.css +103 -0
  26. package/lib/public/css/sidebar.css +7 -0
  27. package/lib/public/css/sticky-notes.css +37 -11
  28. package/lib/public/css/title-bar.css +83 -0
  29. package/lib/public/index.html +50 -63
  30. package/lib/public/modules/account-transport-settings.js +8 -2
  31. package/lib/public/modules/admin-settings-email.js +7 -2
  32. package/lib/public/modules/admin-settings-invitations.js +2 -2
  33. package/lib/public/modules/admin-settings-projects.js +2 -2
  34. package/lib/public/modules/admin-settings-ui.js +20 -4
  35. package/lib/public/modules/admin-settings-users.js +4 -4
  36. package/lib/public/modules/app-connection.js +3 -0
  37. package/lib/public/modules/app-home-hub.js +2 -1
  38. package/lib/public/modules/app-message-router.js +13 -0
  39. package/lib/public/modules/app-messages.js +35 -7
  40. package/lib/public/modules/app-misc.js +3 -1
  41. package/lib/public/modules/app-projects.js +6 -407
  42. package/lib/public/modules/app-rate-limit.js +12 -1
  43. package/lib/public/modules/clay-owner-navigation.js +0 -3
  44. package/lib/public/modules/clay-studio-render.js +3 -1
  45. package/lib/public/modules/clay-tool-presentation.js +1 -0
  46. package/lib/public/modules/command-palette.js +52 -14
  47. package/lib/public/modules/contextual-clay-workspace.js +14 -2
  48. package/lib/public/modules/contextual-clay.js +19 -4
  49. package/lib/public/modules/contextual-mate-context.js +6 -4
  50. package/lib/public/modules/elicitation-form.js +5 -0
  51. package/lib/public/modules/github-star.js +66 -0
  52. package/lib/public/modules/history-replay-batch.js +8 -0
  53. package/lib/public/modules/home-surface-boot.js +0 -1
  54. package/lib/public/modules/input.js +55 -11
  55. package/lib/public/modules/mate-loading-scene.js +17 -0
  56. package/lib/public/modules/message-delivery.js +10 -2
  57. package/lib/public/modules/model-picker.js +2 -0
  58. package/lib/public/modules/pane-activation.js +214 -0
  59. package/lib/public/modules/pane-appearance-bridge.js +59 -0
  60. package/lib/public/modules/pane-binding.js +82 -0
  61. package/lib/public/modules/pane-bridge.js +5 -2
  62. package/lib/public/modules/pane-browser-bridge.js +5 -3
  63. package/lib/public/modules/pane-file-bridge.js +4 -0
  64. package/lib/public/modules/pane-issue-bridge.js +4 -1
  65. package/lib/public/modules/profile.js +11 -3
  66. package/lib/public/modules/project-mate-creation-workspace.js +13 -4
  67. package/lib/public/modules/project-session-visibility-settings.js +19 -16
  68. package/lib/public/modules/project-settings-environment.js +12 -1
  69. package/lib/public/modules/project-settings.js +119 -129
  70. package/lib/public/modules/project-start-dialog.js +130 -0
  71. package/lib/public/modules/project-start-folder-state.js +78 -0
  72. package/lib/public/modules/project-start-folder-view.js +190 -0
  73. package/lib/public/modules/project-start-forms.js +66 -0
  74. package/lib/public/modules/project-start-path.js +60 -0
  75. package/lib/public/modules/project-start-state.js +54 -0
  76. package/lib/public/modules/project-start-submit.js +67 -0
  77. package/lib/public/modules/projects-hub-view.js +32 -44
  78. package/lib/public/modules/projects-hub.js +39 -28
  79. package/lib/public/modules/rewind.js +5 -0
  80. package/lib/public/modules/server-settings.js +36 -91
  81. package/lib/public/modules/session-actions.js +5 -2
  82. package/lib/public/modules/session-context-menu.js +5 -0
  83. package/lib/public/modules/session-rich-drafts.js +21 -0
  84. package/lib/public/modules/session-switch-defer.js +67 -0
  85. package/lib/public/modules/settings-project-target.js +56 -0
  86. package/lib/public/modules/settings-select.js +1 -1
  87. package/lib/public/modules/settings-shell-registry.js +88 -0
  88. package/lib/public/modules/settings-shell-view.js +104 -0
  89. package/lib/public/modules/settings-shell.js +342 -0
  90. package/lib/public/modules/sidebar-mobile.js +8 -30
  91. package/lib/public/modules/sidebar-projects.js +2 -1
  92. package/lib/public/modules/split-group-restore.js +68 -0
  93. package/lib/public/modules/split-pair-ui.js +1 -0
  94. package/lib/public/modules/split-pane-reconciler.js +15 -0
  95. package/lib/public/modules/split-pane-renderer.js +87 -6
  96. package/lib/public/modules/split-session-boundary.js +1 -1
  97. package/lib/public/modules/split-view.js +51 -52
  98. package/lib/public/modules/sticky-notes-checklist.js +51 -6
  99. package/lib/public/modules/stt-controller.js +4 -1
  100. package/lib/public/modules/stt-recording.js +1 -1
  101. package/lib/public/modules/theme.js +31 -3
  102. package/lib/public/modules/tools.js +18 -2
  103. package/lib/public/modules/user-settings-integrations.js +2 -1
  104. package/lib/public/modules/user-settings-layout.js +12 -34
  105. package/lib/public/modules/user-settings-profile.js +1 -1
  106. package/lib/public/modules/user-settings.js +18 -85
  107. package/lib/public/modules/vendor-usage-format.js +93 -0
  108. package/lib/public/modules/vendor-usage-list.js +45 -0
  109. package/lib/public/modules/vendor-usage-model.js +215 -0
  110. package/lib/public/modules/vendor-usage-overview.js +323 -0
  111. package/lib/public/style.css +10 -7
  112. package/lib/public/welcome-art/conversation-desk.png +0 -0
  113. package/lib/public/welcome-art/mate-clay.png +0 -0
  114. package/lib/public/welcome-art/project-clay.png +0 -0
  115. package/lib/sdk-bridge.js +6 -1
  116. package/lib/sdk-message-processor.js +9 -1
  117. package/lib/sdk-quota-snapshot.js +20 -2
  118. package/lib/server-github-stars.js +39 -0
  119. package/lib/server-global-ws.js +21 -17
  120. package/lib/server.js +11 -0
  121. package/lib/session-notes-mcp-server.js +26 -4
  122. package/lib/session-notes-writes.js +88 -0
  123. package/lib/session-visibility.js +20 -8
  124. package/lib/sessions.js +27 -10
  125. package/lib/vendor-usage-acp-readers.js +97 -0
  126. package/lib/vendor-usage-capabilities.js +88 -0
  127. package/lib/vendor-usage-http-readers.js +214 -0
  128. package/lib/vendor-usage-metrics.js +47 -0
  129. package/lib/vendor-usage-native-readers.js +106 -0
  130. package/lib/vendor-usage-overview.js +406 -0
  131. package/lib/vendor-usage-service.js +281 -0
  132. package/lib/ws-schema.js +4 -2
  133. package/lib/yoke/adapters/claude.js +12 -0
  134. package/lib/yoke/adapters/codex-query-contract.js +1 -0
  135. package/lib/yoke/adapters/codex.js +45 -12
  136. package/lib/yoke/claude-hook-installer.js +1 -0
  137. package/lib/yoke/codex-dynamic-tool-names.js +80 -0
  138. package/lib/yoke/codex-model-catalog.js +37 -3
  139. package/lib/yoke/muse-services.js +16 -3
  140. package/lib/yoke/providers/claude-model-descriptions.js +22 -2
  141. package/lib/yoke/vendor-registry.js +6 -2
  142. package/package.json +5 -3
  143. package/lib/public/modules/server-settings-navigation.js +0 -39
@@ -26,6 +26,7 @@ import { clearMcpPermissionModePending } from './mcp-ui.js';
26
26
  import { createWebSocketLifecycle } from './websocket-lifecycle.js';
27
27
  import { createWebSocketWatchdog } from './websocket-watchdog.js';
28
28
  import { resetHistoryReplayBatch } from './history-replay-batch.js';
29
+ import { paneOutgoing } from './pane-activation.js';
29
30
 
30
31
  var connectOverlay = null;
31
32
  var hasConnectedOnce = false;
@@ -371,6 +372,8 @@ export function connect() {
371
372
  var currentWs = getWs();
372
373
  var _origSend = currentWs.send.bind(currentWs);
373
374
  currentWs.send = function (data) {
375
+ data = paneOutgoing(data);
376
+ if (data === null) return false;
374
377
  blinkIO();
375
378
  return _origSend(data);
376
379
  };
@@ -3,7 +3,8 @@
3
3
  import { showProjectsHub, hideProjectsHub } from './projects-hub.js';
4
4
  import { store } from './store.js';
5
5
  import { getWs } from './ws-ref.js';
6
- import { getCachedProjects, openAddProjectModal, switchProject } from './app-projects.js';
6
+ import { getCachedProjects, switchProject } from './app-projects.js';
7
+ import { openAddProjectModal } from './project-start-dialog.js';
7
8
  import { chooseProjectActivationTarget, homeProjectCreationState } from './project-activation.js';
8
9
  import { mateAvatarUrl } from './avatar.js';
9
10
  import { exitDmMode } from './app-dm.js';
@@ -17,6 +17,8 @@ import { handleDefaultAiMessage } from './default-ai.js';
17
17
  import { handleCursorSharingMessage } from './cursor-sharing-preference.js';
18
18
  import { handlePendingMessageQueueMessage } from './pending-message-queue.js';
19
19
  import { receiveHistoryReplay } from './history-replay-batch.js';
20
+ import { filterPaneActivation } from './pane-activation.js';
21
+ import { deferSessionSwitch } from './session-switch-defer.js';
20
22
  import { handleProjectMateCreationMessage } from './project-mate-creation-workspace.js';
21
23
  import { handleProjectDebatePlanningMessage } from './project-debate-planning-workspace.js';
22
24
 
@@ -94,6 +96,17 @@ export function handleHomeProtocolMessage(msg) {
94
96
  }
95
97
 
96
98
  export function processMessage(msg) {
99
+ if (filterPaneActivation(msg)) return;
100
+ if (deferSessionSwitch(msg)) return;
101
+ if (msg.type === 'pane_history_batch') {
102
+ if (!msg.paneActivation || !Array.isArray(msg.items) || msg.items.length > 100) return;
103
+ msg.items.forEach(function (item) {
104
+ // Every original event keeps its order/index and goes through the same
105
+ // stale-binding filter and replay handler as an individual socket event.
106
+ if (item && item.type !== 'pane_history_batch') processMessage(Object.assign({}, item, { paneActivation: msg.paneActivation }));
107
+ });
108
+ return;
109
+ }
97
110
  receiveHistoryReplay(msg, dispatchMessage);
98
111
  }
99
112
 
@@ -1,4 +1,7 @@
1
1
  import { handleStudioDocumentMessage } from './clay-studio-document.js';
2
+ import { saveSessionRichDraft, restoreSessionRichDraft } from './session-rich-drafts.js';
3
+ import { refreshPaneActivation, primePaneSession } from './pane-activation.js';
4
+ import { paneCallbackCurrent } from './pane-binding.js';
2
5
  import { handleClayControlActivity } from './clay-control-activity.js';
3
6
  import { handleContextualWorkspaceMessage } from './contextual-clay-workspace.js';
4
7
  import { handleContextualSessionMessage } from './contextual-clay-session.js';
@@ -62,7 +65,7 @@ import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundE
62
65
  import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch, presentMarkdownEdit } from './filebrowser.js';
63
66
  import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
64
67
  import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
65
- import { handleProjectOwnerChanged } from './project-settings.js';
68
+ import { handleProjectOwnerChanged, handleProjectSettingsEditResult } from './project-settings.js';
66
69
  import { handleProjectEnv, handleProjectEnvSaved } from './project-settings-environment.js';
67
70
  import { handleProjectSessionVisibilityDefault, refreshProjectSessionVisibilitySettings } from './project-session-visibility-settings.js';
68
71
  import { updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleServerSettingResult } from './server-settings.js';
@@ -97,13 +100,15 @@ import { markProjectSessionListHydrated, applyProjectSplitGroups, clearProjectSp
97
100
  import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
98
101
  import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
99
102
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel, markContextUnavailable } from './app-panels.js';
100
- import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress, finishProjectSessionActivation } from './app-projects.js';
103
+ import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, finishProjectSessionActivation } from './app-projects.js';
104
+ import { handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './project-start-dialog.js';
101
105
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
102
106
  import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolStorageResult, handleToolLlmResult, handleToolsError, handleToolControlRequest, handleToolServerState, handleToolServerEvent, handleCapsuleGameSession } from './home-tools.js';
103
107
  import { handleHomeDockState } from './home-dock.js';
104
108
  import { handleToolFrameUrlState } from './home-tool-frame.js';
105
109
  import { enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
106
110
  import { handleRateLimitEvent, updateRateLimitUsage, handleFastModeState } from './app-rate-limit.js';
111
+ import { handleVendorUsageOverview } from './vendor-usage-overview.js';
107
112
  import { handleRemoteCursorMove, handleRemoteCursorLeave, handleRemoteSelection, clearRemoteCursors } from './app-cursors.js';
108
113
  import { showLoopBanner, updateLoopBanner, updateLoopInputVisibility, showRalphApprovalBar, updateRalphApprovalStatus, openRalphPreviewModal, showExecModal, updateExecModalStatus } from './app-loop-ui.js';
109
114
  import { handleAutonomousRunMessage, syncAutonomousRunForSession } from './autonomous-run.js';
@@ -268,6 +273,7 @@ export function processMessage(msg) {
268
273
  break;
269
274
 
270
275
  case "history_done":
276
+ var historyPaintEpoch = store.get('sessionActionEpoch') || 0;
271
277
  if (messagesEl && msg.sessionId === store.get("activeSessionId") && msg.sessionOriginId === store.get("sessionOriginId")) { messagesEl.dataset.controlSessionId = String(msg.sessionId); messagesEl.dataset.controlSessionOrigin = msg.sessionOriginId; messagesEl.dataset.controlHistoryRevision = msg.controlHistoryRevision || ""; }
272
278
  store.set({ replayingHistory: false });
273
279
  resumeThinkingAfterReplay(store.get('sessionIsProcessing'));
@@ -350,6 +356,7 @@ export function processMessage(msg) {
350
356
  // Scroll to tool element if navigating from file edit history
351
357
  if (hasNavTarget) {
352
358
  requestAnimationFrame(function() {
359
+ if (historyPaintEpoch !== (store.get('sessionActionEpoch') || 0) || !paneCallbackCurrent(msg.paneActivation)) return;
353
360
  // Prefer scrolling to the exact tool element
354
361
  var target = nav.toolId ? messagesEl.querySelector('[data-tool-id="' + nav.toolId + '"]') : null;
355
362
  if (!target && nav.assistantUuid) {
@@ -365,7 +372,7 @@ export function processMessage(msg) {
365
372
  }
366
373
  });
367
374
  }
368
- requestAnimationFrame(function () { handlePaletteHistoryDone(); });
375
+ requestAnimationFrame(function () { if (historyPaintEpoch === (store.get('sessionActionEpoch') || 0) && paneCallbackCurrent(msg.paneActivation)) handlePaletteHistoryDone(); });
369
376
  break;
370
377
 
371
378
  case "restore_mate_dm":
@@ -777,9 +784,11 @@ export function processMessage(msg) {
777
784
  markProjectSessionListHydrated();
778
785
  renderMateSessionList(msg.sessions || []);
779
786
  renderSessionList(msg.sessions || []);
787
+ store.set({ paneBindingSessions: msg.sessions || [] });
788
+ refreshPaneActivation();
780
789
  syncGithubSessions(msg.sessions || []);
781
790
  syncPaneTitles();
782
- var panePin = resolvePaneSession(store.get('paneMode'), store.get('panePinPending'), store.get('paneSessionId'), msg.sessions);
791
+ var panePin = resolvePaneSession(store.get('paneMode') && !primePaneSession(), store.get('panePinPending'), store.get('paneSessionId'), msg.sessions);
783
792
  if (panePin.consumed) {
784
793
  store.set({ panePinPending: false });
785
794
  if (panePin.sessionId && getWs() && getWs().readyState === 1) {
@@ -906,6 +915,8 @@ export function processMessage(msg) {
906
915
  break;
907
916
 
908
917
  case "session_switched":
918
+ saveSessionRichDraft();
919
+ store.set({ sessionActionEpoch: (store.get('sessionActionEpoch') || 0) + 1 });
909
920
  clearPairResultStatus();
910
921
  resetStreamBuffer();
911
922
  syncAutonomousRunForSession(msg.autonomousRun || null);
@@ -923,7 +934,7 @@ export function processMessage(msg) {
923
934
  ? msg.runtimeTerminalId
924
935
  : (typeof msg.terminalId === "number" ? msg.terminalId : null);
925
936
  var _sessionRole = msg.sessionRole === "worker" ? "worker" : "driver";
926
- store.set({ activeSessionId: msg.id, sessionOriginId: msg.sessionOriginId || null, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, turnActivity: null, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode'), effectivePermissionMode: msg.effectivePermissionMode || null, permissionCapabilities: msg.permissionCapabilities || { auto: false, mcpOverride: false }, mcpPermissionModeOverrides: msg.mcpPermissionModeOverrides || {}, activeSessionMateId: _sessionRole === "worker" ? null : (store.get('activeProjectMateId') || null), activeSessionMateName: _sessionRole === "worker" ? "" : (store.get('activeProjectMateName') || "") });
937
+ store.set({ activeSessionId: msg.id, sessionOriginId: msg.sessionOriginId || null, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", currentEffort: msg.effort || '', vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, turnActivity: null, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || 'default', effectivePermissionMode: msg.effectivePermissionMode || null, permissionCapabilities: msg.permissionCapabilities || { auto: false, mcpOverride: false }, mcpPermissionModeOverrides: msg.mcpPermissionModeOverrides || {}, activeSessionMateId: _sessionRole === "worker" ? null : (store.get('activeProjectMateId') || null), activeSessionMateName: _sessionRole === "worker" ? "" : (store.get('activeProjectMateName') || "") });
927
938
  handleScheduledTaskSessionSwitched(msg);
928
939
  requestLoopInterviewState();
929
940
  activateDeliverySession(msg.id, msg.cliSessionId || null, msg.sessionOriginId || null);
@@ -998,14 +1009,19 @@ export function processMessage(msg) {
998
1009
  // Restore draft for incoming session
999
1010
  var draft = store.get('sessionDrafts')[store.get('activeSessionId')] || "";
1000
1011
  inputEl.value = draft;
1012
+ restoreSessionRichDraft();
1001
1013
  autoResize();
1002
1014
  if (!("ontouchstart" in window)) {
1003
1015
  inputEl.focus();
1004
1016
  }
1005
1017
  // Reload survival: a restored active session that is a split-group
1006
1018
  // member reopens its group (no-op when a split is already open).
1007
- finishProjectSessionActivation();
1008
- maybeRestoreSplitGroup();
1019
+ var deferredSwitch = store.get('deferredSessionSwitch');
1020
+ if (!deferredSwitch || !deferredSwitch.draining || deferredSwitch.finalSwitch === msg) {
1021
+ finishProjectSessionActivation();
1022
+ maybeRestoreSplitGroup();
1023
+ handlePaletteSessionSwitch(true);
1024
+ }
1009
1025
  break;
1010
1026
 
1011
1027
  case "session_full_access_changed":
@@ -1350,6 +1366,11 @@ export function processMessage(msg) {
1350
1366
  stopUrgentBlink();
1351
1367
  break;
1352
1368
 
1369
+ case "ask_user_pending":
1370
+ renderAskUserQuestion(msg.toolId, msg.input);
1371
+ startUrgentBlink();
1372
+ break;
1373
+
1353
1374
  case "permission_request_pending":
1354
1375
  store.set({ turnActivity: null });
1355
1376
  setActivity(null);
@@ -1596,6 +1617,10 @@ export function processMessage(msg) {
1596
1617
  updateRateLimitUsage(msg);
1597
1618
  break;
1598
1619
 
1620
+ case "vendor_usage_overview":
1621
+ handleVendorUsageOverview(msg);
1622
+ break;
1623
+
1599
1624
  case "auto_continue_scheduled":
1600
1625
  // Scheduler auto-continue, just show info
1601
1626
  break;
@@ -1770,6 +1795,7 @@ export function processMessage(msg) {
1770
1795
  store.set({ pendingTermCommand: null });
1771
1796
  // Small delay to let terminal initialize
1772
1797
  setTimeout(function() {
1798
+ if (!paneCallbackCurrent(msg.paneActivation)) return;
1773
1799
  sendTerminalCommand(cmd);
1774
1800
  }, 300);
1775
1801
  }
@@ -1902,12 +1928,14 @@ export function processMessage(msg) {
1902
1928
  break;
1903
1929
 
1904
1930
  case "set_project_title_result":
1931
+ handleProjectSettingsEditResult(msg);
1905
1932
  if (!msg.ok) {
1906
1933
  showToast(msg.error || "Failed to rename project", "error");
1907
1934
  }
1908
1935
  break;
1909
1936
 
1910
1937
  case "set_project_icon_result":
1938
+ handleProjectSettingsEditResult(msg);
1911
1939
  if (!msg.ok) {
1912
1940
  showToast(msg.error || "Failed to set icon", "error");
1913
1941
  }
@@ -3,6 +3,8 @@
3
3
 
4
4
  import { refreshIcons, iconHtml } from './icons.js';
5
5
  import { getWs } from './ws-ref.js';
6
+ import { store } from './store.js';
7
+ import { sessionCallback } from './pane-binding.js';
6
8
  import { updateBrowserTabList } from './context-sources.js';
7
9
  import { setExtensionConnected } from './mcp-ui.js';
8
10
  import { initPasteModal } from './paste-modal.js';
@@ -270,7 +272,7 @@ export function showConfirm(text, onConfirm, okLabel, destructive, onCancel) {
270
272
  confirmCancelCallback = null;
271
273
  if (replaced) replaced();
272
274
  confirmText.textContent = text;
273
- confirmCallback = onConfirm;
275
+ confirmCallback = store.get('paneCommittedBinding') ? sessionCallback(onConfirm) : onConfirm;
274
276
  confirmCancelCallback = onCancel || null;
275
277
  confirmOk.textContent = okLabel || "Delete";
276
278
  confirmOk.className = "confirm-btn " + (destructive === false ? "confirm-ok" : "confirm-delete");
@@ -1,11 +1,11 @@
1
1
  import { prepareContextualWorkspaceDeparture } from './contextual-clay-workspace.js';
2
+ import { checkComposerDeparture, saveSessionRichDraft } from './session-rich-drafts.js';
2
3
  // app-projects.js - Project list, switching, add/remove project modals
3
4
  // Extracted from app.js (PR-29)
4
5
 
5
6
  import { showProjectsHub, hideProjectsHub, completeProjectsOnboarding } from './projects-hub.js';
6
- import { escapeHtml, showToast } from './utils.js';
7
+ import { showToast } from './utils.js';
7
8
  import { refreshIcons } from './icons.js';
8
- import { parseEmojis } from './markdown.js';
9
9
  import { store } from './store.js';
10
10
  import { resetModelPickerState } from './model-picker.js';
11
11
  import { getWs, setWs } from './ws-ref.js';
@@ -49,157 +49,6 @@ var cachedRemovedProjects = [];
49
49
  var pendingRemoveSlug = null;
50
50
  var pendingRemoveName = null;
51
51
 
52
- // Add-project modal state
53
- var addProjectModal = null;
54
- var addProjectInput = null;
55
- var addProjectCreateInput = null;
56
- var addProjectCloneInput = null;
57
- var addProjectCloneProgress = null;
58
- var addProjectSuggestions = null;
59
- var addProjectError = null;
60
- var addProjectOk = null;
61
- var addProjectCancel = null;
62
- var addProjectModeBtns = null;
63
- var addProjectPanels = null;
64
- var addProjectRemoved = null;
65
- var addProjectPrefixEl = null;
66
- var addProjectPrefixValue = "";
67
- var addProjectDebounce = null;
68
- var addProjectActiveIdx = -1;
69
- var addProjectMode = "existing";
70
-
71
- export function initProjects() {
72
- // Init add-project modal DOM refs
73
- addProjectModal = document.getElementById("add-project-modal");
74
- addProjectInput = document.getElementById("add-project-input");
75
- addProjectCreateInput = document.getElementById("add-project-create-input");
76
- addProjectCloneInput = document.getElementById("add-project-clone-input");
77
- addProjectCloneProgress = document.getElementById("add-project-clone-progress");
78
- addProjectSuggestions = document.getElementById("add-project-suggestions");
79
- addProjectError = document.getElementById("add-project-error");
80
- addProjectOk = document.getElementById("add-project-ok");
81
- addProjectCancel = document.getElementById("add-project-cancel");
82
- addProjectModeBtns = addProjectModal.querySelectorAll(".add-project-mode-btn");
83
- addProjectPanels = addProjectModal.querySelectorAll(".add-project-panel");
84
- addProjectRemoved = document.getElementById("add-project-removed");
85
- addProjectPrefixEl = document.getElementById("add-project-prefix");
86
-
87
- // Mode button click listeners
88
- for (var mbi = 0; mbi < addProjectModeBtns.length; mbi++) {
89
- addProjectModeBtns[mbi].addEventListener("click", function () {
90
- if (this.disabled) return;
91
- switchAddProjectMode(this.dataset.mode);
92
- });
93
- }
94
-
95
- // Existing project input listeners
96
- addProjectInput.addEventListener("focus", function () {
97
- var val = addProjectInput.value;
98
- if ((val || addProjectPrefixValue) && addProjectSuggestions.children.length === 0) {
99
- requestBrowseDir(val);
100
- } else if (addProjectSuggestions.children.length > 0) {
101
- addProjectSuggestions.classList.remove("hidden");
102
- }
103
- });
104
-
105
- addProjectModal.querySelector(".confirm-dialog").addEventListener("click", function (e) {
106
- if (e.target === addProjectInput || addProjectInput.contains(e.target)) return;
107
- if (e.target === addProjectSuggestions || addProjectSuggestions.contains(e.target)) return;
108
- addProjectSuggestions.classList.add("hidden");
109
- addProjectActiveIdx = -1;
110
- });
111
-
112
- addProjectInput.addEventListener("input", function () {
113
- var val = addProjectInput.value;
114
- addProjectError.classList.add("hidden");
115
- if (addProjectDebounce) clearTimeout(addProjectDebounce);
116
- addProjectDebounce = setTimeout(function () {
117
- requestBrowseDir(val);
118
- }, 200);
119
- });
120
-
121
- addProjectInput.addEventListener("keydown", function (e) {
122
- var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
123
-
124
- if (e.key === "ArrowDown") {
125
- e.preventDefault();
126
- if (items.length > 0) {
127
- var next = addProjectActiveIdx < items.length - 1 ? addProjectActiveIdx + 1 : 0;
128
- setActiveIdx(next);
129
- }
130
- return;
131
- }
132
-
133
- if (e.key === "ArrowUp") {
134
- e.preventDefault();
135
- if (items.length > 0) {
136
- var prev = addProjectActiveIdx > 0 ? addProjectActiveIdx - 1 : items.length - 1;
137
- setActiveIdx(prev);
138
- }
139
- return;
140
- }
141
-
142
- if (e.key === "Tab") {
143
- e.preventDefault();
144
- var target = addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length
145
- ? items[addProjectActiveIdx]
146
- : items.length > 0 ? items[0] : null;
147
- if (target) {
148
- var fullP = target.dataset.path + "/";
149
- addProjectInput.value = stripPrefix(fullP);
150
- addProjectError.classList.add("hidden");
151
- requestBrowseDir(addProjectInput.value);
152
- }
153
- return;
154
- }
155
-
156
- if (e.key === "Enter") {
157
- e.preventDefault();
158
- if (addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length) {
159
- var fullPicked = items[addProjectActiveIdx].dataset.path + "/";
160
- addProjectInput.value = stripPrefix(fullPicked);
161
- addProjectError.classList.add("hidden");
162
- requestBrowseDir(addProjectInput.value);
163
- return;
164
- }
165
- submitAddProject();
166
- return;
167
- }
168
-
169
- if (e.key === "Escape") {
170
- e.preventDefault();
171
- closeAddProjectModal();
172
- return;
173
- }
174
- });
175
-
176
- // Enter key on create/clone inputs
177
- addProjectCreateInput.addEventListener("keydown", function (e) {
178
- if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
179
- if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
180
- });
181
-
182
- addProjectCloneInput.addEventListener("keydown", function (e) {
183
- if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
184
- if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
185
- });
186
-
187
- addProjectOk.addEventListener("click", function () { submitAddProject(); });
188
- addProjectCancel.addEventListener("click", function () { closeAddProjectModal(); });
189
-
190
- // Close on backdrop click
191
- addProjectModal.querySelector(".confirm-backdrop").addEventListener("click", function () {
192
- closeAddProjectModal();
193
- });
194
-
195
- // Project list add button
196
- var projectListAddBtn = document.getElementById("project-list-add");
197
- if (projectListAddBtn) {
198
- projectListAddBtn.addEventListener("click", function () {
199
- openAddProjectModal();
200
- });
201
- }
202
- }
203
52
 
204
53
  // --- State accessors ---
205
54
 
@@ -391,6 +240,10 @@ export function resetClientState() {
391
240
 
392
241
  export function switchProject(slug) {
393
242
  if (!slug) return;
243
+ if (slug !== store.get('currentSlug')) {
244
+ try { checkComposerDeparture(); saveSessionRichDraft(); }
245
+ catch (error) { showToast(error.message, 'warn'); return; }
246
+ }
394
247
  if (prepareContextualWorkspaceDeparture(slug) === false) return;
395
248
  var target = cachedProjects.find(function (project) { return project.slug === slug; });
396
249
  if (target && target.contentAccess === false && target.administrativeAccess === true) {
@@ -638,257 +491,3 @@ export function handleRemoveProjectResult(msg) {
638
491
  showToast(msg.error || "Failed to remove project", "error");
639
492
  }
640
493
  }
641
-
642
- // --- Add project modal ---
643
-
644
- function switchAddProjectMode(mode) {
645
- addProjectMode = mode;
646
- for (var mi = 0; mi < addProjectModeBtns.length; mi++) {
647
- var btn = addProjectModeBtns[mi];
648
- if (btn.dataset.mode === mode) {
649
- btn.classList.add("active");
650
- } else {
651
- btn.classList.remove("active");
652
- }
653
- }
654
- for (var pi = 0; pi < addProjectPanels.length; pi++) {
655
- var panel = addProjectPanels[pi];
656
- if (panel.dataset.panel === mode) {
657
- panel.classList.add("active");
658
- } else {
659
- panel.classList.remove("active");
660
- }
661
- }
662
- addProjectError.classList.add("hidden");
663
- addProjectCloneProgress.classList.add("hidden");
664
- if (mode === "existing") {
665
- addProjectOk.textContent = "Add";
666
- } else if (mode === "create") {
667
- addProjectOk.textContent = "Create";
668
- } else if (mode === "clone") {
669
- addProjectOk.textContent = "Clone";
670
- }
671
- setTimeout(function () {
672
- if (mode === "existing") {
673
- addProjectInput.focus();
674
- } else if (mode === "create") {
675
- addProjectCreateInput.focus();
676
- } else if (mode === "clone") {
677
- addProjectCloneInput.focus();
678
- }
679
- }, 50);
680
- }
681
-
682
- export function openAddProjectModal(mode) {
683
- addProjectModal.classList.remove("hidden");
684
- addProjectCreateInput.value = "";
685
- addProjectCloneInput.value = "";
686
- addProjectError.classList.add("hidden");
687
- addProjectError.textContent = "";
688
- addProjectCloneProgress.classList.add("hidden");
689
- addProjectSuggestions.classList.add("hidden");
690
- addProjectSuggestions.innerHTML = "";
691
- addProjectActiveIdx = -1;
692
- addProjectOk.disabled = false;
693
- var existingBtn = addProjectModal.querySelector('.add-project-mode-btn[data-mode="existing"]');
694
- var st = store.snap();
695
- if (st.isOsUsers) {
696
- existingBtn.disabled = false;
697
- var myUser = st.cachedAllUsers.find(function (u) { return u.id === st.myUserId; });
698
- var isAdmin = myUser && myUser.role === "admin";
699
- if (!isAdmin && myUser && myUser.linuxUser) {
700
- // Non-admin: lock prefix to home directory
701
- addProjectPrefixValue = "/home/" + myUser.linuxUser + "/";
702
- addProjectPrefixEl.textContent = addProjectPrefixValue;
703
- addProjectPrefixEl.classList.remove("hidden");
704
- addProjectInput.value = "";
705
- addProjectInput.placeholder = "subdirectory";
706
- } else {
707
- // Admin: no prefix restriction
708
- addProjectPrefixValue = "";
709
- addProjectPrefixEl.classList.add("hidden");
710
- addProjectInput.value = "/";
711
- addProjectInput.placeholder = "/";
712
- }
713
- switchAddProjectMode("existing");
714
- } else {
715
- addProjectPrefixValue = "";
716
- addProjectPrefixEl.classList.add("hidden");
717
- addProjectInput.value = "/";
718
- addProjectInput.placeholder = "/";
719
- existingBtn.disabled = false;
720
- switchAddProjectMode("existing");
721
- }
722
- if (mode === "create" || mode === "clone") switchAddProjectMode(mode);
723
- renderRemovedProjectsList();
724
- }
725
-
726
- function renderRemovedProjectsList() {
727
- if (!addProjectRemoved) return;
728
- addProjectRemoved.innerHTML = "";
729
- if (!cachedRemovedProjects || cachedRemovedProjects.length === 0) {
730
- addProjectRemoved.classList.add("hidden");
731
- return;
732
- }
733
- addProjectRemoved.classList.remove("hidden");
734
- for (var ri = 0; ri < cachedRemovedProjects.length; ri++) {
735
- var rp = cachedRemovedProjects[ri];
736
- var item = document.createElement("div");
737
- item.className = "add-project-removed-item";
738
- item.dataset.path = rp.path;
739
- item.addEventListener("click", function () {
740
- var p = this.dataset.path;
741
- var ws = getWs();
742
- if (ws && ws.readyState === 1) {
743
- ws.send(JSON.stringify({ type: "add_project", path: p }));
744
- }
745
- closeAddProjectModal();
746
- });
747
- var iconEl = document.createElement("span");
748
- iconEl.className = "add-project-removed-icon";
749
- iconEl.textContent = rp.icon || "\uD83D\uDCC1";
750
- item.appendChild(iconEl);
751
- var info = document.createElement("div");
752
- info.className = "add-project-removed-info";
753
- var nameEl = document.createElement("div");
754
- nameEl.className = "add-project-removed-name";
755
- nameEl.textContent = rp.title || rp.path.split("/").pop() || rp.path;
756
- info.appendChild(nameEl);
757
- var pathEl = document.createElement("div");
758
- pathEl.className = "add-project-removed-path";
759
- pathEl.textContent = rp.path;
760
- info.appendChild(pathEl);
761
- item.appendChild(info);
762
- addProjectRemoved.appendChild(item);
763
- }
764
- try { parseEmojis(addProjectRemoved); } catch (e) {}
765
- }
766
-
767
- export function closeAddProjectModal() {
768
- addProjectModal.classList.add("hidden");
769
- addProjectInput.value = "";
770
- addProjectCreateInput.value = "";
771
- addProjectCloneInput.value = "";
772
- addProjectSuggestions.classList.add("hidden");
773
- addProjectSuggestions.innerHTML = "";
774
- addProjectError.classList.add("hidden");
775
- addProjectCloneProgress.classList.add("hidden");
776
- addProjectActiveIdx = -1;
777
- addProjectPrefixValue = "";
778
- addProjectPrefixEl.classList.add("hidden");
779
- if (addProjectDebounce) { clearTimeout(addProjectDebounce); addProjectDebounce = null; }
780
- }
781
-
782
- function getFullPath(inputVal) {
783
- return addProjectPrefixValue + inputVal;
784
- }
785
-
786
- function stripPrefix(fullPath) {
787
- if (addProjectPrefixValue && fullPath.indexOf(addProjectPrefixValue) === 0) {
788
- return fullPath.slice(addProjectPrefixValue.length);
789
- }
790
- return fullPath;
791
- }
792
-
793
- function requestBrowseDir(val) {
794
- var ws = getWs();
795
- if (!ws || ws.readyState !== 1) return;
796
- ws.send(JSON.stringify({ type: "browse_dir", path: getFullPath(val) }));
797
- }
798
-
799
- export function handleBrowseDirResult(msg) {
800
- addProjectSuggestions.innerHTML = "";
801
- addProjectActiveIdx = -1;
802
- if (msg.error) {
803
- addProjectSuggestions.classList.add("hidden");
804
- return;
805
- }
806
- var entries = msg.entries || [];
807
- if (entries.length === 0) {
808
- addProjectSuggestions.classList.add("hidden");
809
- return;
810
- }
811
- for (var si = 0; si < entries.length; si++) {
812
- var entry = entries[si];
813
- var item = document.createElement("div");
814
- item.className = "add-project-suggestion-item";
815
- item.dataset.path = entry.path;
816
- item.innerHTML = '<i data-lucide="folder"></i><span class="add-project-suggestion-name">' +
817
- escapeHtml(entry.name) + '</span>';
818
- item.addEventListener("click", function (e) {
819
- var fullP = this.dataset.path + "/";
820
- addProjectInput.value = stripPrefix(fullP);
821
- addProjectInput.focus();
822
- addProjectError.classList.add("hidden");
823
- requestBrowseDir(addProjectInput.value);
824
- });
825
- addProjectSuggestions.appendChild(item);
826
- }
827
- addProjectSuggestions.classList.remove("hidden");
828
- refreshIcons();
829
- }
830
-
831
- export function handleAddProjectResult(msg) {
832
- addProjectCloneProgress.classList.add("hidden");
833
- if (msg.ok) {
834
- closeAddProjectModal();
835
- if (msg.existing) {
836
- showToast("Project already registered", "info");
837
- } else {
838
- var toastMsg = addProjectMode === "create" ? "Project created" : addProjectMode === "clone" ? "Project cloned" : "Project added";
839
- showToast(toastMsg, "success");
840
- }
841
- if (msg.slug) switchProject(msg.slug);
842
- } else {
843
- addProjectError.textContent = msg.error || "Failed to add project";
844
- addProjectError.classList.remove("hidden");
845
- addProjectOk.disabled = false;
846
- }
847
- }
848
-
849
- export function handleCloneProgress(msg) {
850
- if (msg.status === "cloning") {
851
- addProjectCloneProgress.classList.remove("hidden");
852
- }
853
- }
854
-
855
- function setActiveIdx(idx) {
856
- var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
857
- addProjectActiveIdx = idx;
858
- for (var ai = 0; ai < items.length; ai++) {
859
- if (ai === idx) {
860
- items[ai].classList.add("active");
861
- items[ai].scrollIntoView({ block: "nearest" });
862
- } else {
863
- items[ai].classList.remove("active");
864
- }
865
- }
866
- }
867
-
868
- function submitAddProject() {
869
- addProjectError.classList.add("hidden");
870
- addProjectOk.disabled = true;
871
-
872
- if (addProjectMode === "existing") {
873
- var val = getFullPath(addProjectInput.value).replace(/\/+$/, "");
874
- if (!val) { addProjectOk.disabled = false; return; }
875
- var ws = getWs();
876
- if (ws && ws.readyState === 1) {
877
- ws.send(JSON.stringify({ type: "add_project", path: val }));
878
- }
879
- } else if (addProjectMode === "create") {
880
- var name = addProjectCreateInput.value.trim();
881
- if (!name) { addProjectOk.disabled = false; return; }
882
- var ws2 = getWs();
883
- if (ws2 && ws2.readyState === 1) {
884
- ws2.send(JSON.stringify({ type: "create_project", name: name }));
885
- }
886
- } else if (addProjectMode === "clone") {
887
- var url = addProjectCloneInput.value.trim();
888
- if (!url) { addProjectOk.disabled = false; return; }
889
- var ws3 = getWs();
890
- if (ws3 && ws3.readyState === 1) {
891
- ws3.send(JSON.stringify({ type: "clone_project", url: url }));
892
- }
893
- }
894
- }