clay-server 5.2.1-beta.1 → 5.3.0-beta.1

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 (151) hide show
  1. package/lib/clay-control-presentation-binding.js +5 -3
  2. package/lib/clay-product-control-tools.js +3 -3
  3. package/lib/clay-product-control.js +4 -2
  4. package/lib/clay-screen-controls.js +18 -3
  5. package/lib/clay-split-view-awareness.js +35 -0
  6. package/lib/clay-split-view-controls.js +122 -0
  7. package/lib/clay-split-view-input.js +20 -0
  8. package/lib/clay-split-view-render-proof.js +22 -0
  9. package/lib/pane-asset-manifest.js +63 -0
  10. package/lib/project-connection.js +19 -10
  11. package/lib/project-session-actions.js +1 -0
  12. package/lib/project-session-delete.js +2 -2
  13. package/lib/project-session-folders.js +1 -0
  14. package/lib/project-sessions.js +6 -1
  15. package/lib/project-split-view-document.js +33 -0
  16. package/lib/project-split-view-names.js +67 -0
  17. package/lib/project-split-view-organization.js +23 -0
  18. package/lib/project-split-views.js +479 -0
  19. package/lib/project-worker-conversation.js +158 -0
  20. package/lib/project-worker-creation-proposal.js +3 -4
  21. package/lib/project-worker-proposal.js +1 -1
  22. package/lib/project-worker-removal.js +101 -0
  23. package/lib/project.js +28 -1
  24. package/lib/public/app.js +32 -13
  25. package/lib/public/css/base.css +9 -0
  26. package/lib/public/css/filebrowser.css +0 -1
  27. package/lib/public/css/pane-header-controls.css +100 -0
  28. package/lib/public/css/pane.css +24 -5
  29. package/lib/public/css/popup-chat.css +123 -0
  30. package/lib/public/css/right-workbench.css +50 -18
  31. package/lib/public/css/split-view-layout.css +460 -0
  32. package/lib/public/css/split-view-worker-activity.css +97 -0
  33. package/lib/public/css/sticky-notes.css +13 -0
  34. package/lib/public/index.html +3 -3
  35. package/lib/public/modules/app-connection.js +4 -1
  36. package/lib/public/modules/app-messages.js +25 -8
  37. package/lib/public/modules/chat-render-runtime.js +45 -0
  38. package/lib/public/modules/clay-control-copy.js +3 -0
  39. package/lib/public/modules/clay-navigation-render.js +1 -1
  40. package/lib/public/modules/clay-owner-navigation.js +15 -5
  41. package/lib/public/modules/clay-split-view-screen.js +74 -0
  42. package/lib/public/modules/clay-tool-activity.js +5 -0
  43. package/lib/public/modules/command-palette.js +30 -28
  44. package/lib/public/modules/contextual-clay-session.js +26 -2
  45. package/lib/public/modules/contextual-clay.js +5 -2
  46. package/lib/public/modules/contextual-mate-sessions.js +2 -0
  47. package/lib/public/modules/generation-stop.js +7 -1
  48. package/lib/public/modules/knowledge-workbench-entry.js +43 -0
  49. package/lib/public/modules/markdown.js +11 -0
  50. package/lib/public/modules/mate-knowledge-workbench.js +2 -7
  51. package/lib/public/modules/mate-sidebar.js +1 -1
  52. package/lib/public/modules/palette-clay-conversation.js +87 -0
  53. package/lib/public/modules/palette-render-helpers.js +25 -0
  54. package/lib/public/modules/pane-activation.js +46 -7
  55. package/lib/public/modules/pane-binding.js +12 -2
  56. package/lib/public/modules/pane-bridge.js +12 -3
  57. package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
  58. package/lib/public/modules/pane-feature-assets.js +35 -0
  59. package/lib/public/modules/pane-search-receipt.js +12 -0
  60. package/lib/public/modules/permission-control.js +6 -6
  61. package/lib/public/modules/popup-chat-rendering.js +244 -0
  62. package/lib/public/modules/project-activation.js +2 -2
  63. package/lib/public/modules/qrcode.js +12 -1
  64. package/lib/public/modules/right-workbench.js +68 -0
  65. package/lib/public/modules/search-hints.js +79 -0
  66. package/lib/public/modules/session-context-menu.js +35 -2
  67. package/lib/public/modules/session-folder-context.js +24 -13
  68. package/lib/public/modules/session-folder-delete.js +18 -1
  69. package/lib/public/modules/session-folder-layout.js +7 -6
  70. package/lib/public/modules/session-search-pane-state.js +30 -0
  71. package/lib/public/modules/session-search.js +53 -13
  72. package/lib/public/modules/sidebar-mobile.js +3 -3
  73. package/lib/public/modules/sidebar-projects.js +1 -0
  74. package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
  75. package/lib/public/modules/sidebar-sessions.js +57 -24
  76. package/lib/public/modules/sidebar.js +20 -3
  77. package/lib/public/modules/split-group-helpers.js +1 -1
  78. package/lib/public/modules/split-group-restore.js +8 -11
  79. package/lib/public/modules/split-pair-ui.js +18 -0
  80. package/lib/public/modules/split-pane-header-state.js +45 -0
  81. package/lib/public/modules/split-pane-reconciler.js +23 -4
  82. package/lib/public/modules/split-pane-renderer.js +330 -23
  83. package/lib/public/modules/split-pane-retention.js +157 -0
  84. package/lib/public/modules/split-session-boundary.js +21 -0
  85. package/lib/public/modules/split-view-create-action.js +15 -0
  86. package/lib/public/modules/split-view-create-menu.js +83 -0
  87. package/lib/public/modules/split-view-create-request.js +23 -0
  88. package/lib/public/modules/split-view-layout-client.js +198 -0
  89. package/lib/public/modules/split-view-layout-icons.js +37 -0
  90. package/lib/public/modules/split-view-layout-scope.js +38 -0
  91. package/lib/public/modules/split-view-organization.js +73 -0
  92. package/lib/public/modules/split-view-presentation-close.js +35 -0
  93. package/lib/public/modules/split-view-presentation-drag.js +254 -0
  94. package/lib/public/modules/split-view-presentation-drop.js +57 -0
  95. package/lib/public/modules/split-view-presentation-lookup.js +44 -0
  96. package/lib/public/modules/split-view-presentation-renderer.js +494 -0
  97. package/lib/public/modules/split-view-presentations.js +495 -0
  98. package/lib/public/modules/split-view-shell-controls.js +28 -0
  99. package/lib/public/modules/split-view-sidebar-menu.js +42 -0
  100. package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
  101. package/lib/public/modules/split-view-singleton.js +21 -0
  102. package/lib/public/modules/split-view-worker-activity.js +94 -0
  103. package/lib/public/modules/split-view-worker-controls.js +30 -0
  104. package/lib/public/modules/split-view-worker-removal.js +137 -0
  105. package/lib/public/modules/split-view-worker-scroll.js +57 -0
  106. package/lib/public/modules/split-view-worker-transcript.js +46 -0
  107. package/lib/public/modules/split-view-workers.js +352 -0
  108. package/lib/public/modules/split-view.js +61 -22
  109. package/lib/public/modules/sticky-notes.js +9 -2
  110. package/lib/public/modules/tools.js +25 -5
  111. package/lib/public/modules/workbench-layout-preference.js +76 -0
  112. package/lib/public/pane-assets/app-OJEQXBBS.js +1 -0
  113. package/lib/public/pane-assets/chunk-4SMIVAB4.js +1 -0
  114. package/lib/public/pane-assets/chunk-623V5CEO.js +1 -0
  115. package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
  116. package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
  117. package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
  118. package/lib/public/pane-assets/chunk-GML2R5JE.js +167 -0
  119. package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
  120. package/lib/public/pane-assets/chunk-I5P3MWQG.js +1 -0
  121. package/lib/public/pane-assets/chunk-UVGOPFEX.js +2 -0
  122. package/lib/public/pane-assets/manifest.json +551 -0
  123. package/lib/public/pane-assets/style-GN7R6XKV.css +1 -0
  124. package/lib/public/style.css +5 -1
  125. package/lib/sdk-bridge.js +16 -3
  126. package/lib/server-owner-ui.js +6 -2
  127. package/lib/server-owner-view-observation.js +34 -0
  128. package/lib/server-palette.js +1 -0
  129. package/lib/server-pane-bootstrap.js +24 -0
  130. package/lib/server-settings.js +2 -0
  131. package/lib/server-static-assets.js +52 -0
  132. package/lib/server-workbench-layout-settings.js +56 -0
  133. package/lib/server.js +6 -45
  134. package/lib/session-activity-state.js +28 -0
  135. package/lib/session-list-delivery.js +17 -0
  136. package/lib/session-pair-turn-control.js +1 -6
  137. package/lib/session-search.js +3 -0
  138. package/lib/session-split-group-anchors.js +2 -2
  139. package/lib/session-split-group-roles.js +5 -8
  140. package/lib/session-split-group-v2-store.js +0 -3
  141. package/lib/sessions.js +16 -6
  142. package/lib/split-view-layout.js +132 -0
  143. package/lib/users-workbench-layout-preferences.js +33 -0
  144. package/lib/users.js +3 -0
  145. package/lib/workspace-query-service.js +1 -5
  146. package/lib/ws-schema.js +15 -0
  147. package/lib/yoke/adapters/codex.js +1 -0
  148. package/lib/yoke/codex-app-server.js +3 -1
  149. package/lib/yoke/codex-idle-recovery.js +73 -20
  150. package/lib/yoke/codex-recovery-admission.js +65 -4
  151. package/package.json +6 -3
@@ -1,3 +1,4 @@
1
+ import { handleWorkerRemovalResult } from './split-view-worker-removal.js';
1
2
  import { handleStudioDocumentMessage } from './clay-studio-document.js';
2
3
  import { saveSessionRichDraft, restoreSessionRichDraft } from './session-rich-drafts.js';
3
4
  import { refreshPaneActivation, primePaneSession } from './pane-activation.js';
@@ -48,11 +49,13 @@ import { handleDefaultVendorMessage } from './default-vendor.js';
48
49
  import { handleContextViewMessage } from './context-view-preference.js';
49
50
  import { handleHomeMateMemoryState, handleHomeMateKnowledgeState, syncHomeMateSettingsTarget, handleHomeMateAvatarError } from './home-mate-settings.js';
50
51
  import { renderKnowledgeList, handleKnowledgeContent, handleKnowledgeMutation } from './mate-knowledge.js';
51
- import { handleKnowledgeWorkbenchMessage } from './mate-knowledge-workbench.js';
52
+ import { handleKnowledgeWorkbenchMessage } from './knowledge-workbench-entry.js';
52
53
  import { renderMemoryList } from './mate-memory.js';
53
54
  import { handlePaletteSessionSwitch, handlePaletteHistoryDone, handlePaletteHistoryPrepended, setPaletteVersion } from './command-palette.js';
54
55
  import { handleFindInSessionResults } from './session-search.js';
55
56
  import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
57
+ import { handleProjectSplitViews, handleProjectSplitViewResult, requestProjectSplitViews } from './split-view-presentations.js';
58
+ import { handleSplitWorkerConversation } from './split-view-workers.js';
56
59
  import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
57
60
  import { handleSessionActionMessage } from './session-actions.js';
58
61
  import { renderWorkerProposal, updateWorkerProposal } from './worker-proposal.js';
@@ -95,7 +98,7 @@ import { clearPairResultStatus, handlePairResultStatus } from './pair-result-sta
95
98
  import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, resetStreamBuffer, appendDelta, flushStreamBuffer, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, showSuggestionChips, armStickyBottom, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
96
99
  import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot, updateCrossProjectBlink } from './app-favicon.js';
97
100
  import { setStatus } from './app-connection.js';
98
- import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
101
+ import { resolveSwitchedVendor } from './pane-session.js';
99
102
  import { markProjectSessionListHydrated, applyProjectSplitGroups, clearProjectSplitState } from './split-session-boundary.js';
100
103
  import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
101
104
  import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
@@ -440,6 +443,7 @@ export function processMessage(msg) {
440
443
  if (msg.projectOwnerId !== undefined) store.set({ currentProjectOwnerId: msg.projectOwnerId });
441
444
  if (msg.ownerLocked !== undefined) store.set({ ownerLocked: !!msg.ownerLocked });
442
445
  if (msg.osUsers !== undefined) store.set({ isOsUsers: !!msg.osUsers });
446
+ store.set({ paneOsIdentity: msg.paneOsIdentity || null });
443
447
  if (msg.lanHost) window.__lanHost = msg.lanHost;
444
448
  store.set({ skipPermsEnabled: msg.dangerouslySkipPermissions === true });
445
449
  var spBanner = document.getElementById("skip-perms-pill");
@@ -785,19 +789,28 @@ export function processMessage(msg) {
785
789
  renderMateSessionList(msg.sessions || []);
786
790
  renderSessionList(msg.sessions || []);
787
791
  store.set({ paneBindingSessions: msg.sessions || [] });
792
+ requestProjectSplitViews();
788
793
  refreshPaneActivation();
789
794
  syncGithubSessions(msg.sessions || []);
790
795
  syncPaneTitles();
791
- var panePin = resolvePaneSession(store.get('paneMode') && !primePaneSession(), store.get('panePinPending'), store.get('paneSessionId'), msg.sessions);
792
- if (panePin.consumed) {
793
- store.set({ panePinPending: false });
794
- if (panePin.sessionId && getWs() && getWs().readyState === 1) {
795
- getWs().send(JSON.stringify({ type: "switch_session", id: panePin.sessionId }));
796
- }
796
+ if (store.get('paneMode')) {
797
+ // A pane pin must use the authenticated activation binding. Sending
798
+ // this startup URL's session as a plain switch can race a parent
799
+ // retarget and restore the iframe's stale initial session.
800
+ primePaneSession();
797
801
  }
798
802
  handlePaletteSessionSwitch();
799
803
  maybeRestoreSplitGroup();
800
804
  break;
805
+ case "project_split_views":
806
+ handleProjectSplitViews(msg);
807
+ break;
808
+ case "project_split_view_result":
809
+ handleProjectSplitViewResult(msg);
810
+ break;
811
+ case "split_worker_conversation":
812
+ handleSplitWorkerConversation(msg);
813
+ break;
801
814
  case "session_github_status":
802
815
  applyGithubPersonalReviewStatus(msg);
803
816
  break;
@@ -813,6 +826,10 @@ export function processMessage(msg) {
813
826
  if (!msg.ok && msg.error) showToast(msg.error, "error");
814
827
  break;
815
828
 
829
+ case "split_worker_remove_result":
830
+ handleWorkerRemovalResult(msg);
831
+ break;
832
+
816
833
  case "split_worker_close_result":
817
834
  if (!msg.ok && msg.error) showToast(msg.error, "error");
818
835
  break;
@@ -60,6 +60,32 @@ var stickyBottomCeilingMs = 8000;
60
60
  var stickyBottomResizeObs = null;
61
61
  var stickyBottomInputBound = false;
62
62
 
63
+ function layoutScrollLock() { return store.get('paneLayoutScrollLock'); }
64
+
65
+ export function beginPaneLayoutScrollLock(token, scrollTop) {
66
+ var messagesEl = getMessagesEl();
67
+ if (!messagesEl || !token) return false;
68
+ disarmStickyBottom();
69
+ var userPosition = store.get('paneLastUserScrollTop');
70
+ var position = Number.isFinite(userPosition) ? userPosition : Number.isFinite(scrollTop) ? Math.max(0, scrollTop) : messagesEl.scrollTop;
71
+ store.set({ paneLayoutScrollLock: { token: token, scrollTop: position } });
72
+ messagesEl.scrollTop = position;
73
+ return true;
74
+ }
75
+
76
+ export function endPaneLayoutScrollLock(token) {
77
+ var state = layoutScrollLock(), messagesEl = getMessagesEl();
78
+ if (!state || state.token !== token || !messagesEl) return false;
79
+ requestAnimationFrame(function () {
80
+ var current = layoutScrollLock();
81
+ if (!current || current.token !== token || !messagesEl.isConnected) return;
82
+ disarmStickyBottom();
83
+ messagesEl.scrollTop = current.scrollTop;
84
+ store.set({ paneLayoutScrollLock: null });
85
+ });
86
+ return true;
87
+ }
88
+
63
89
  export function getStickyBottom() { return stickyBottom; }
64
90
 
65
91
  function pinToBottomNow() {
@@ -95,6 +121,7 @@ function ensureStickyInfrastructure() {
95
121
  }
96
122
 
97
123
  export function armStickyBottom(durationMs) {
124
+ if (layoutScrollLock()) return;
98
125
  if (prependAnchor) return;
99
126
  ensureStickyInfrastructure();
100
127
  stickyBottom = true;
@@ -128,6 +155,22 @@ export function disarmStickyBottom() {
128
155
  }
129
156
 
130
157
  export function initRendering() {
158
+ var messagesEl = getMessagesEl();
159
+ if (messagesEl && !messagesEl.__paneLayoutScrollLockListener) {
160
+ messagesEl.__paneLayoutScrollLockListener = true;
161
+ messagesEl.addEventListener('wheel', function () {
162
+ var revision = (store.get('paneUserScrollRevision') || 0) + 1;
163
+ store.set({ paneUserScrollRevision: revision, paneUserScrollIntent: revision });
164
+ requestAnimationFrame(function () {
165
+ if (store.get('paneUserScrollIntent') === revision) store.set({ paneUserScrollIntent: null });
166
+ });
167
+ }, { passive: true });
168
+ messagesEl.addEventListener('scroll', function () {
169
+ var state = layoutScrollLock();
170
+ if (state) store.set({ paneLayoutScrollLock: Object.assign({}, state, { scrollTop: messagesEl.scrollTop }) });
171
+ if (store.get('paneUserScrollIntent') != null) store.set({ paneLastUserScrollTop: messagesEl.scrollTop, paneUserScrollIntent: null });
172
+ }, { passive: true });
173
+ }
131
174
  var lastAssistantPlaceholder = '';
132
175
  function updateInputPlaceholder(state) {
133
176
  var inputEl = document.getElementById("input");
@@ -200,6 +243,7 @@ export function keepActiveThinkingAtTail() {
200
243
  }
201
244
 
202
245
  export function scrollToBottom() {
246
+ if (layoutScrollLock()) return;
203
247
  if (prependAnchor || store.get('replayingHistory')) return;
204
248
  var messagesEl = getMessagesEl();
205
249
  if (!messagesEl) return;
@@ -214,6 +258,7 @@ export function scrollToBottom() {
214
258
  return;
215
259
  }
216
260
  requestAnimationFrame(function () {
261
+ if (layoutScrollLock()) return;
217
262
  var dist = messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight;
218
263
  if (dist > 150) {
219
264
  isUserScrolledUp = true;
@@ -8,6 +8,7 @@ var actions = {
8
8
  runtime: 'return this conversation to GUI chat', queue_add: 'queue the reviewed message for the Driver',
9
9
  queue_edit: 'update the reviewed queued message', queue_cancel: 'cancel the reviewed queued message',
10
10
  queue_reorder: 'reorder the reviewed message queue', navigate: 'open the reviewed conversation so we can continue there',
11
+ split_view: 'change the reviewed Split view',
11
12
  dismiss_notifications: 'dismiss the reviewed notifications', profile: 'update the reviewed Mate profile',
12
13
  send: 'deliver the reviewed message to this Mate', assign: 'prepare the requested Driver work for your review', follow_up: 'prepare a follow-up for the existing Driver', document: 'open the requested document for editing', sketch: 'present the requested sketch', browser: 'operate the requested shared browser tab'
13
14
  };
@@ -20,6 +21,8 @@ export function activityCopy(msg) {
20
21
  rendered: 'The reviewed conversation is visibly open in the selected window.',
21
22
  cancelled: 'I left the change unapplied.', failed: 'I could not finish this change. Please check the result before trying again.',
22
23
  partial: 'Part of this change took effect, but the operation failed. Please check the result.', unknown: 'I cannot confirm the outcome. Please check before trying again.' };
24
+ if (msg.domain === 'screen' && msg.action === 'split_view' && msg.phase === 'acknowledged') return 'I started opening the reviewed Split view. Its visible result is still unconfirmed.';
25
+ if (msg.domain === 'screen' && msg.action === 'split_view' && msg.phase === 'rendered') return 'The reviewed Split view and ready panes are visibly open in the selected window.';
23
26
  if (msg.domain === 'assignment') { var work = { review: 'I prepared the Driver work. Please review its proposal before it starts.', queued: 'I submitted the approved Driver work for dispatch.', running: 'The Driver is working on the approved request.', completed: 'The Driver finished this request.', cancelled: 'I left this Driver request unapplied.', unknown: 'I cannot confirm the Driver outcome. Please inspect the work before retrying.' }; return work[msg.phase] || endings[msg.phase] || null; }
24
27
  if (msg.domain === 'studio' && msg.phase === 'rendered') return 'The requested workbench content is visibly rendered in this window.';
25
28
  if (msg.domain === 'studio' && msg.phase === 'render_unknown') return 'The service returned, but I could not confirm that the workbench content is visible.';
@@ -1,7 +1,7 @@
1
1
  import { store } from './store.js';
2
2
  import { getWs } from './ws-ref.js';
3
3
 
4
- function visible(element) {
4
+ export function visible(element) {
5
5
  if (!element || !element.isConnected || element.closest('[hidden], [inert], [aria-hidden="true"]')) return false;
6
6
  var box = element.getBoundingClientRect(), style = getComputedStyle(element);
7
7
  if (style.visibility !== 'visible' || Number(style.opacity) === 0 || box.width <= 0 || box.height <= 0) return false;
@@ -1,26 +1,34 @@
1
+ import { handleSplitViewProbe, splitViewScreenSnapshot, applySplitViewNavigation, observeSplitViewRendering } from './clay-split-view-screen.js';
1
2
  import { observeNavigationRendering } from './clay-navigation-render.js';
2
3
  import { showClayNavigationActivity, clearClayControlActivity } from './clay-control-activity.js';
3
4
  import { store } from './store.js';
4
5
  import { getWs } from './ws-ref.js';
5
6
  import { navigateToSession } from './command-palette.js';
7
+ var offerSocket = null;
6
8
  function context() {
7
- return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('dmMode'), store.get('homeShellVisible'), store.get('homeChatMateId'), store.get('homeChatSessionId'), store.get('homeSubSurface'), (store.get('contextualClay') || {}).mode]);
9
+ return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('dmMode'), store.get('homeShellVisible'), store.get('homeChatMateId'), store.get('homeChatSessionId'), store.get('homeSubSurface'), (store.get('contextualClay') || {}).mode, splitViewScreenSnapshot()]);
8
10
  }
9
11
  export function handleOwnerNavigation(msg) {
12
+ if (handleSplitViewProbe(msg)) return true;
10
13
  if (msg.type !== 'clay_navigation_offer' && msg.type !== 'clay_navigation_apply') return false;
11
14
  var ws = getWs();
12
15
  if (!ws || ws.readyState !== 1) return true;
13
16
  if (msg.type === 'clay_navigation_offer') {
14
17
  if (document.hidden || store.get('paneMode')) return true;
15
- store.set({ clayNavigationOffer: { id: msg.deliveryId, context: context(), socket: ws } });
18
+ offerSocket = ws;
19
+ store.set({ clayNavigationOffer: { id: msg.deliveryId, context: context() } });
16
20
  ws.send(JSON.stringify({ type: 'clay_navigation_claim', deliveryId: msg.deliveryId }));
17
21
  return true;
18
22
  }
19
23
  var offer = store.get('clayNavigationOffer');
20
24
  store.set({ clayNavigationOffer: null });
21
- var accepted = offer && offer.id === msg.deliveryId && offer.socket === ws && offer.context === context() && !document.hidden && !store.get('paneMode');
25
+ var accepted = offer && offer.id === msg.deliveryId && offerSocket === ws && offer.context === context() && !document.hidden && !store.get('paneMode');
26
+ offerSocket = null;
22
27
  var status = 'refused';
23
- if (accepted && msg.target && typeof msg.target.projectSlug === 'string' && Number.isSafeInteger(msg.target.sessionId)) {
28
+ if (accepted && msg.target && msg.target.surface === 'split_view') {
29
+ try { status = applySplitViewNavigation(msg.target) ? 'accepted' : 'refused'; } catch (error) { status = 'failed'; }
30
+ }
31
+ if (accepted && msg.target && msg.target.surface !== 'split_view' && typeof msg.target.projectSlug === 'string' && Number.isSafeInteger(msg.target.sessionId)) {
24
32
  try {
25
33
  showClayNavigationActivity(msg.target, false); navigateToSession(msg.target, null, msg.target.entryIndex); status = 'accepted';
26
34
  } catch (error) { showClayNavigationActivity(msg.target, true); status = 'failed'; }
@@ -28,6 +36,8 @@ export function handleOwnerNavigation(msg) {
28
36
  if (status === 'refused') clearClayControlActivity();
29
37
  // This acknowledges navigator invocation only. Rendering may finish after reconnect.
30
38
  if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'clay_navigation_ack', deliveryId: msg.deliveryId, status: status }));
31
- if (status === 'accepted') observeNavigationRendering(msg, ws);
39
+ if (status === 'accepted') {
40
+ if (msg.target.surface === 'split_view') observeSplitViewRendering(msg, ws); else observeNavigationRendering(msg, ws);
41
+ }
32
42
  return true;
33
43
  }
@@ -0,0 +1,74 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { livePaneBinding } from './pane-binding.js';
4
+ import { applyReviewedSplitView } from './split-view-presentations.js';
5
+ import { visible } from './clay-navigation-render.js';
6
+
7
+ var attempts = new Map();
8
+ export function splitViewScreenSnapshot() {
9
+ var state = store.snap();
10
+ var view = (state.projectSplitViews || []).find(function (item) { return item.id === state.projectSplitViewActiveId; });
11
+ var hidden = !!(document.hidden || state.paneMode || !state.connected || state.dmMode || state.homeShellVisible);
12
+ return { projectSlug: state.currentSlug || null, ownerId: state.myUserId || null, hidden: hidden,
13
+ viewId: view ? view.id : null, revision: view ? view.revision : 0, tree: view ? view.tree : null,
14
+ focusOrigin: state.projectSplitViewFocusedOrigin || null, singletonOrigin: state.projectSplitViewSingletonOrigin || null,
15
+ activeSessionId: state.activeSessionId || null,
16
+ panes: Array.from(document.querySelectorAll('.split-pane-frame')).slice(0, 32).map(function (frame) {
17
+ var live = frame.clayPane && frame.clayPane.identity(), binding = live && live.binding;
18
+ var ready = !!livePaneBinding(frame), cached = !!frame.closest('.split-pane-cached');
19
+ return { origin: frame.dataset.sessionOriginId || '', sessionId: Number(frame.dataset.sessionId) || 0,
20
+ visible: !hidden && !cached && visible(frame), cached: cached, ready: ready,
21
+ binding: ready && binding ? { requestId: binding.requestId, generation: binding.generation, sessionId: binding.sessionId, sessionOriginId: binding.sessionOriginId } : null };
22
+ }) };
23
+ }
24
+ function stableObservation(key, socket, deadline, complete, ready) {
25
+ if (attempts.has(key)) return;
26
+ if (attempts.size >= 32) return;
27
+ var animation, timer, previous, stable = 0, owner = store.get('myUserId'), slug = store.get('currentSlug'), ended = false;
28
+ function finish(snapshot) {
29
+ if (ended) return;
30
+ ended = true; clearTimeout(timer); cancelAnimationFrame(animation); attempts.delete(key);
31
+ document.removeEventListener('click', stop, true); document.removeEventListener('visibilitychange', hidden);
32
+ if (getWs() === socket && socket.readyState === 1) complete(snapshot);
33
+ }
34
+ function stop(event) { if (event.target.closest('#generation-stop, #autonomous-run-stop, #send-btn.stop')) finish(null); }
35
+ function hidden() { if (document.hidden) finish(null); }
36
+ document.addEventListener('click', stop, true); document.addEventListener('visibilitychange', hidden);
37
+ attempts.set(key, finish);
38
+ timer = setTimeout(function () { finish(null); }, Math.max(0, Math.min(5000, deadline - Date.now())));
39
+ function inspect() {
40
+ if (ended) return;
41
+ if (getWs() !== socket || !store.get('connected') || store.get('myUserId') !== owner || store.get('currentSlug') !== slug) { finish(null); return; }
42
+ var snapshot = splitViewScreenSnapshot(), serialized = JSON.stringify(snapshot);
43
+ stable = previous === serialized ? stable + 1 : 1; previous = serialized;
44
+ if (stable >= 2 && (!ready || ready(snapshot))) finish(snapshot); else animation = requestAnimationFrame(inspect);
45
+ }
46
+ animation = requestAnimationFrame(inspect);
47
+ }
48
+ export function handleSplitViewProbe(msg) {
49
+ if (msg.type !== 'clay_view_probe') return false;
50
+ var socket = getWs();
51
+ if (!socket || socket.readyState !== 1 || store.get('paneMode')) return true;
52
+ stableObservation(msg.probeId, socket, Date.now() + 4500, function (snapshot) {
53
+ socket.send(JSON.stringify({ type: 'clay_view_observed', probeId: msg.probeId, snapshot: snapshot }));
54
+ });
55
+ return true;
56
+ }
57
+ export function applySplitViewNavigation(target) {
58
+ if (JSON.stringify(splitViewScreenSnapshot()) !== JSON.stringify(target.expectedScreen)) return false;
59
+ return applyReviewedSplitView(target);
60
+ }
61
+ function renderedTarget(snapshot, target) {
62
+ var exact = snapshot && !snapshot.hidden && snapshot.viewId === target.view.id && snapshot.revision === target.view.revision &&
63
+ snapshot.focusOrigin === target.focusOrigin && JSON.stringify(snapshot.tree) === JSON.stringify(target.view.tree);
64
+ var expected = target.view.sessions.filter(function (session) { return !target.focusOrigin || session.sessionOriginId === target.focusOrigin; });
65
+ var shown = snapshot ? snapshot.panes.filter(function (pane) { return pane.visible; }) : [];
66
+ return !!(exact && shown.length === expected.length && expected.every(function (session) { return shown.some(function (pane) { return pane.origin === session.sessionOriginId && pane.sessionId === session.id && pane.ready && !pane.cached; }); }));
67
+ }
68
+ export function observeSplitViewRendering(msg, socket) {
69
+ var target = msg.target;
70
+ stableObservation(msg.deliveryId, socket, msg.renderDeadline, function (snapshot) {
71
+ socket.send(JSON.stringify({ type: 'clay_navigation_rendered', deliveryId: msg.deliveryId, renderToken: msg.renderToken,
72
+ surface: 'split_view', visible: renderedTarget(snapshot, target), snapshot: snapshot || splitViewScreenSnapshot() }));
73
+ }, function (snapshot) { return renderedTarget(snapshot, target); });
74
+ }
@@ -17,6 +17,11 @@ export function syncToolGroupDisclosure(el) {
17
17
  var expanded = !el.classList.contains('collapsed');
18
18
  el.querySelector('.tool-group-header').setAttribute('aria-expanded', String(expanded));
19
19
  var items = el.querySelector('.tool-group-items');
20
+ if (el.classList.contains('popup-chat-activity') && typeof document !== 'undefined' && document.body && document.body.classList.contains('popup-chat-rendering')) {
21
+ items.inert = false;
22
+ items.setAttribute('aria-hidden', 'false');
23
+ return;
24
+ }
20
25
  items.inert = !expanded;
21
26
  items.setAttribute('aria-hidden', String(!expanded));
22
27
  }
@@ -1,3 +1,4 @@
1
+ import { formatRelativeDate, paletteSelectionIndex } from './palette-render-helpers.js';
1
2
  import { forwardContextualSessionCitation, validContextualCitation, contextualCitationLink } from './contextual-session-citations.js';
2
3
  import { mateAvatarUrl } from './avatar.js';
3
4
  import { escapeHtml } from './utils.js';
@@ -7,23 +8,10 @@ import { startSearchClayChat, retrySearchClayAsk } from './search-clay-chat.js';
7
8
  import { getWs } from './ws-ref.js';
8
9
  import { openProjectLog } from './project-logs.js';
9
10
  import { store } from './store.js';
11
+ import { initSearchHints, setSearchHintsPaletteOpen } from './search-hints.js';
12
+ import { openPaletteClayConversation } from './palette-clay-conversation.js';
10
13
  import { disarmStickyBottom } from './chat-render-runtime.js';
11
14
 
12
- function formatRelativeDate(ts) {
13
- if (!ts) return "";
14
- var diff = Math.max(0, Date.now() - ts);
15
- var min = Math.floor(diff / 60000);
16
- if (min < 1) return "just now";
17
- if (min < 60) return min + "m ago";
18
- var hr = Math.floor(min / 60);
19
- if (hr < 24) return hr + "h ago";
20
- var days = Math.floor(hr / 24);
21
- if (days < 7) return days + "d ago";
22
- if (days < 30) return Math.floor(days / 7) + "w ago";
23
- var d = new Date(ts);
24
- var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
25
- return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
26
- }
27
15
 
28
16
  var ctx;
29
17
  var paletteEl = null, inputEl = null, resultsEl = null, footerEl = null;
@@ -34,12 +22,14 @@ var searchResults = [], searchQuery = "", searchPending = false;
34
22
 
35
23
  export function initCommandPalette(value) {
36
24
  ctx = value;
25
+ if (paletteEl) return;
37
26
  buildDOM();
38
27
  // The palette dialog stops bubbling so backdrop clicks cannot close it.
39
28
  // Capture session controls before that boundary; the same handler also
40
29
  // serves links rendered in the full Home transcript.
41
30
  document.addEventListener("click", handleClaySessionLinkClick, true);
42
31
  var trigger = document.getElementById("cmd-palette-btn");
32
+ initSearchHints(trigger, inputEl);
43
33
  if (trigger) {
44
34
  var isMac = navigator.platform.indexOf("Mac") !== -1;
45
35
  var kbd = trigger.querySelector(".cmd-palette-searchbar-kbd");
@@ -154,6 +144,7 @@ export function isCommandPaletteOpen() { return paletteEl && !paletteEl.classLis
154
144
  export function openCommandPalette() {
155
145
  if (!paletteEl) return;
156
146
  paletteEl.classList.remove("hidden");
147
+ setSearchHintsPaletteOpen(true);
157
148
  var trigger = document.getElementById("cmd-palette-btn");
158
149
  if (trigger) trigger.style.visibility = "hidden";
159
150
  updateFooter();
@@ -162,8 +153,10 @@ export function openCommandPalette() {
162
153
  }
163
154
 
164
155
  export function closeCommandPalette() {
156
+ if ((store.get('paletteClayConversation') || {}).phase === 'resolving') store.set({ paletteClayConversation: null });
165
157
  if (!paletteEl) return;
166
158
  paletteEl.classList.add("hidden");
159
+ setSearchHintsPaletteOpen(false);
167
160
  var trigger = document.getElementById("cmd-palette-btn");
168
161
  if (trigger) trigger.style.visibility = "";
169
162
  if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }
@@ -297,7 +290,7 @@ function buildDOM() {
297
290
  '<div class="cmd-palette-input-row">' +
298
291
  '<img class="cmd-palette-input-brand" src="/clay-studio-symbol.png" width="22" height="22" alt="">' +
299
292
  '<i data-lucide="search" aria-hidden="true"></i>' +
300
- '<input class="cmd-palette-input" type="text" placeholder="Search conversations, or ask Clay…" autocomplete="off" spellcheck="false" />' +
293
+ '<input class="cmd-palette-input" type="text" aria-label="Search or ask Clay" placeholder="Search or ask Clay, like “What was I working on recently?”" autocomplete="off" spellcheck="false" />' +
301
294
  '<button type="button" class="cmd-palette-kbd" id="cmd-palette-close" aria-label="Close search"><i data-lucide="x"></i></button>' +
302
295
  '</div>' +
303
296
  '<div class="cmd-palette-results"></div>' +
@@ -313,11 +306,13 @@ function buildDOM() {
313
306
  inputEl.addEventListener("input", function () {
314
307
  var query = inputEl.value.trim();
315
308
  searchQuery = query;
309
+ if (abortCtrl) { abortCtrl.abort(); abortCtrl = null; }
316
310
  searchResults = [];
317
311
  searchPending = !!query;
318
312
  renderSearch();
319
313
  if (debounceTimer) clearTimeout(debounceTimer);
320
314
  if (query) debounceTimer = setTimeout(function () { fetchHomeSearchResults(query); }, 250);
315
+ else fetchHomeData();
321
316
  });
322
317
  inputEl.addEventListener("keydown", function (event) {
323
318
  if (event.key === "Escape") { event.preventDefault(); closeCommandPalette(); return; }
@@ -347,13 +342,15 @@ function updateFooter() {
347
342
  function fetchHomeData() {
348
343
  if (cachedHomeData.length) { renderSearch(); return; }
349
344
  if (abortCtrl) abortCtrl.abort();
350
- abortCtrl = new AbortController();
345
+ var request = abortCtrl = new AbortController();
351
346
  fetch("/api/palette/search", { signal: abortCtrl.signal }).then(function (res) { return res.json(); }).then(function (data) {
347
+ if (abortCtrl !== request) return;
352
348
  abortCtrl = null;
353
349
  cachedHomeData = data.results || [];
354
350
  renderSearch();
355
351
  }).catch(function (error) {
356
352
  if (error.name === "AbortError") return;
353
+ if (abortCtrl !== request) return;
357
354
  abortCtrl = null;
358
355
  cachedHomeData = [];
359
356
  renderSearch();
@@ -362,8 +359,9 @@ function fetchHomeData() {
362
359
 
363
360
  function fetchHomeSearchResults(query) {
364
361
  if (abortCtrl) abortCtrl.abort();
365
- abortCtrl = new AbortController();
362
+ var request = abortCtrl = new AbortController();
366
363
  fetch("/api/palette/search?q=" + encodeURIComponent(query), { signal: abortCtrl.signal }).then(function (res) { return res.json(); }).then(function (data) {
364
+ if (abortCtrl !== request) return;
367
365
  abortCtrl = null;
368
366
  if (searchQuery !== query) return;
369
367
  searchResults = data.results || [];
@@ -371,6 +369,7 @@ function fetchHomeSearchResults(query) {
371
369
  renderSearch();
372
370
  }).catch(function (error) {
373
371
  if (error.name === "AbortError") return;
372
+ if (abortCtrl !== request) return;
374
373
  abortCtrl = null;
375
374
  if (searchQuery !== query) return;
376
375
  searchPending = false;
@@ -396,6 +395,7 @@ function sessionIcon(session) {
396
395
  }
397
396
 
398
397
  function renderSearch() {
398
+ var previous = items[activeIndex];
399
399
  var query = searchQuery.toLowerCase();
400
400
  var sessions = query ? cachedHomeData.filter(function (item) {
401
401
  return (item.sessionTitle || "").toLowerCase().indexOf(query) !== -1 || (item.projectTitle || "").toLowerCase().indexOf(query) !== -1;
@@ -410,6 +410,11 @@ function renderSearch() {
410
410
  items.push({ type: 'retry-clay-ask' });
411
411
  html += renderItem(index++, '<img src="/clay-studio-symbol.png" width="22" height="22" alt="">', 'Retry previous question', escapeHtml(pendingAsk.text), null, null, ' is-retry-clay-ask');
412
412
  }
413
+ if (query) {
414
+ items.push({ type: "ask-clay", query: searchQuery });
415
+ html += '<div class="cmd-palette-group-label">Ask Clay</div>';
416
+ html += renderItem(index++, '<img src="/clay-studio-symbol.png" width="22" height="22" alt="">', 'Ask Clay about “' + escapeHtml(searchQuery) + '”', "Start a new Clay conversation", null, null, " is-ask-clay");
417
+ }
413
418
  if (sessions.length) {
414
419
  html += '<div class="cmd-palette-group-label">' + (query ? "Conversations" : "Recent conversations") + '</div>';
415
420
  for (var s = 0; s < sessions.length; s++) {
@@ -420,17 +425,13 @@ function renderSearch() {
420
425
  html += renderItem(index++, sessionIcon(session), escapeHtml(session.sessionTitle || "New Chat"), project, snippet, session.lastActivity, "");
421
426
  }
422
427
  }
423
- if (query) {
424
- items.push({ type: "ask-clay", query: searchQuery });
425
- if (!sessions.length && !searchPending) html += '<div class="cmd-palette-exact-empty">No exact matches. Clay can search by meaning.</div>';
426
- html += '<div class="cmd-palette-group-label">Ask Clay</div>';
427
- html += renderItem(index, '<img src="/clay-studio-symbol.png" width="22" height="22" alt="">', 'Ask Clay about “' + escapeHtml(searchQuery) + '”', "Start a new Clay conversation", null, null, " is-ask-clay");
428
- }
428
+ if (query && !sessions.length && !searchPending) html += '<div class="cmd-palette-exact-empty">No exact matches. Clay can search by meaning.</div>';
429
429
  if (!html) html = '<div class="cmd-palette-empty">No conversations yet.</div>';
430
430
  resultsEl.innerHTML = html;
431
- activeIndex = -1;
431
+ activeIndex = paletteSelectionIndex(previous, items, query);
432
432
  refreshIcons();
433
433
  bindItemEvents();
434
+ if (activeIndex >= 0) setActive(activeIndex, true);
434
435
  }
435
436
 
436
437
  function renderItem(index, icon, title, desc, snippet, timestamp, extraClass) {
@@ -455,16 +456,17 @@ function setActive(index, skipScroll) {
455
456
  for (var i = 0; i < elements.length; i++) elements[i].classList.toggle("active", i === index);
456
457
  if (!skipScroll && elements[index]) elements[index].scrollIntoView({ block: "nearest" });
457
458
  }
458
-
459
459
  function beginClayChat(query) {
460
460
  if (!isCommandPaletteOpen()) return;
461
461
  if (startSearchClayChat(query)) closeCommandPalette();
462
462
  }
463
-
464
463
  function activateItem(entry) {
465
464
  if (entry.type === 'retry-clay-ask') { if (retrySearchClayAsk()) closeCommandPalette(); return; }
466
465
  if (entry.type === "ask-clay") { beginClayChat(entry.query); return; }
467
- if (entry.type === "mate-session" || entry.type === "session") navigateToSession(entry.data, entry.query);
466
+ if (entry.type === "mate-session" || entry.type === "session") {
467
+ if (!store.get('paneMode') && (entry.data.isMate || /^mate-/.test(entry.data.projectSlug)) && openPaletteClayConversation(entry.data, entry.query, entry.data.entryIndex)) return;
468
+ navigateToSession(entry.data, entry.query, entry.data.entryIndex);
469
+ }
468
470
  }
469
471
 
470
472
  export function navigateToSession(item, query, entryIndex) {
@@ -1,16 +1,28 @@
1
1
  // Session identity remains pinned across transport recovery and presentation.
2
+ import { navigateToSession } from './command-palette.js';
2
3
  import { autoResize, takeComposerDraft, restoreComposerDraft } from './input.js';
3
4
  import { store } from './store.js';
4
5
  import { withdrawPaneControlActivity } from './clay-control-activity.js';
5
6
  import { initContextualComposer } from './contextual-composer.js';
6
7
  import { submitContextualAsk, pauseContextualAsk, stopContextualAsk } from './contextual-ask-session.js';
8
+ import { resolveProjectAssistantIdentity } from './project-chat-identity.js';
9
+ import { setPopupChatRendering, popupChatContextIsReady } from './popup-chat-rendering.js';
10
+ import { syncPopupChatToolGroups } from './tools.js';
7
11
 
8
12
  // Session replay and authenticated account bootstrap complete independently.
9
13
  function submissionReady() { var expected = window.frameElement && window.frameElement.dataset.expectedSessionOrigin; return (!expected || expected === store.get('sessionOriginId')) && !!(store.get('contextualSessionReady') && store.get('connected') && store.get('myUserId') && store.get('sessionOriginId') && store.get('activeSessionId') === store.get('paneSessionId')); }
10
14
  function enabled() { return store.get('paneMode') && new URLSearchParams(location.search).get('contextual') === '1'; }
11
- function renderSessionAvailability() {
15
+ function renderSessionAvailability(notifyReady) {
12
16
  var input = document.getElementById('input');
13
17
  var frame = window.frameElement;
18
+ var state = store.snap();
19
+ var identity = resolveProjectAssistantIdentity();
20
+ if (state.contextualSessionReady && frame && !frame.hidden) frame.dataset.sessionId = String(state.paneSessionId);
21
+ var popupSurface = null;
22
+ try { popupSurface = window.parent.document.getElementById('contextual-clay'); } catch (error) { /* Popup rendering stays native outside its authenticated parent surface. */ }
23
+ var popupReady = popupChatContextIsReady(document.body, state, frame, identity, popupSurface);
24
+ setPopupChatRendering(popupReady);
25
+ syncPopupChatToolGroups(popupReady);
14
26
  var mate = frame && (store.get('cachedMatesList') || []).find(function (item) { return item.id === frame.dataset.mateId; });
15
27
  var name = mate && ((mate.profile || {}).displayName || mate.displayName || mate.name);
16
28
  if (input && name) { input.setAttribute('aria-label', 'Message ' + name); if (name.length > 32) input.placeholder = 'Message this Mate…'; }
@@ -24,7 +36,7 @@ function renderSessionAvailability() {
24
36
  if (surface.dataset.mode === 'popover' && window.parent.document.activeElement === anchor) document.getElementById('input').focus({ preventScroll: true });
25
37
  }
26
38
  } catch (error) { /* Cross-origin authentication owns focus. */ }
27
- if (store.get('contextualSessionReady') && window.frameElement && !window.frameElement.hidden) {
39
+ if (notifyReady !== false && store.get('contextualSessionReady') && window.frameElement && !window.frameElement.hidden) {
28
40
  window.frameElement.dataset.sessionId = String(store.get('paneSessionId'));
29
41
  var parentSurface = window.parent.document.getElementById('contextual-clay');
30
42
  if (parentSurface) parentSurface.dispatchEvent(new window.parent.CustomEvent('contextual-clay-ready'));
@@ -67,8 +79,20 @@ export function initContextualSession() {
67
79
  } catch (error) { /* Authentication navigation owns its document. */ }
68
80
  });
69
81
  var surfaceBridge = window.parent.document.getElementById('contextual-clay');
82
+ if (window.frameElement && surfaceBridge && typeof MutationObserver !== 'undefined') {
83
+ // Visibility changes update presentation and focus, never session readiness.
84
+ // A ready notification renders the parent, which can mutate these attributes.
85
+ var popupVisibilityObserver = new MutationObserver(function () { renderSessionAvailability(false); });
86
+ popupVisibilityObserver.observe(window.frameElement, { attributes: true, attributeFilter: ['hidden', 'inert'] });
87
+ popupVisibilityObserver.observe(surfaceBridge, { attributes: true, attributeFilter: ['data-mode'] });
88
+ }
70
89
  if (surfaceBridge && window.frameElement && surfaceBridge.contains(window.frameElement)) window.frameElement.conversation = {
71
90
  source: window, document: document,
91
+ navigateEntry: function (target) {
92
+ if (!submissionReady() || target.ownerId !== store.get('myUserId') || target.mateId !== store.get('activeProjectMateId') || target.sessionId !== store.get('paneSessionId') || target.sessionOriginId !== store.get('sessionOriginId')) return false;
93
+ navigateToSession({ projectSlug: store.get('currentSlug'), sessionId: target.sessionId }, target.query, target.entryIndex);
94
+ return true;
95
+ },
72
96
  submitAsk: submitContextualAsk,
73
97
  pauseAsk: pauseContextualAsk,
74
98
  stopAsk: stopContextualAsk,
@@ -61,7 +61,7 @@ function render() {
61
61
  renderContextualWorkspaceBack();
62
62
  if (!surface) return;
63
63
  surface.hidden = mode === 'closed' || mode === 'workspace';
64
- surface.dataset.mode = mode;
64
+ if (surface.dataset.mode !== mode) surface.dataset.mode = mode;
65
65
  positionContextualClay();
66
66
  surface.querySelector('[data-clay-expand]').hidden = mode !== 'popover';
67
67
  surface.querySelector('[data-clay-close]').hidden = mode !== 'popover' && mode !== 'opening';
@@ -100,7 +100,10 @@ function render() {
100
100
  var selectedFrame = contextualFrame();
101
101
  var connection = selectedFrame && selectedFrame.conversation;
102
102
  surface.querySelector('[data-clay-loading]').hidden = !!(connection && connection.identity().ready) || surface.dataset.askPending === 'true';
103
- surface.querySelectorAll('iframe').forEach(function (frame) { frame.hidden = frame !== selectedFrame; });
103
+ surface.querySelectorAll('iframe').forEach(function (frame) {
104
+ var hidden = frame !== selectedFrame;
105
+ if (frame.hidden !== hidden) frame.hidden = hidden;
106
+ });
104
107
  }
105
108
 
106
109
  function createFrame(mate, sessionId) {
@@ -1,4 +1,5 @@
1
1
  // Saved conversations are independent of which compact renderers are mounted.
2
+ import { handlePaletteClayConversation } from './palette-clay-conversation.js';
2
3
  import { store } from './store.js';
3
4
  import { getWs } from './ws-ref.js';
4
5
  import { contextualMate, contextualMateStamp, contextualFrame } from './contextual-mate-identity.js';
@@ -111,6 +112,7 @@ export function attachContextualConversations(surface) {
111
112
  refreshIcons(); render();
112
113
  }
113
114
  export function handleContextualConversations(msg) {
115
+ if (handlePaletteClayConversation(msg)) return true;
114
116
  if (msg.type !== 'contextual_mate_sessions_result') return false;
115
117
  var kind = requestState('list') && requestState('list').requestId === msg.requestId ? 'list' : 'action';
116
118
  var request = requestState(kind);
@@ -5,7 +5,13 @@ import { isDriverOperatedView } from './worker-pane-lock.js';
5
5
  export function stopGeneration() {
6
6
  var ws = getWs();
7
7
  if (!store.get('processing') || !store.get('connected') || !ws || ws.readyState !== 1) return false;
8
- ws.send(JSON.stringify({ type: 'stop' }));
8
+ var message = { type: 'stop' };
9
+ if (store.get('paneMode')) {
10
+ var binding = store.get('paneCommittedBinding');
11
+ if (!store.get('paneReady') || !binding || binding.sessionId !== store.get('activeSessionId') || binding.sessionOriginId !== store.get('sessionOriginId')) return false;
12
+ message.paneBinding = binding;
13
+ }
14
+ ws.send(JSON.stringify(message));
9
15
  return true;
10
16
  }
11
17