clay-server 5.3.0-beta.5 → 5.3.0-beta.7

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 (185) hide show
  1. package/lib/clay-product-control-tools.js +2 -2
  2. package/lib/clay-product-control.js +1 -1
  3. package/lib/clay-screen-controls.js +1 -0
  4. package/lib/clay-split-view-awareness.js +5 -2
  5. package/lib/clay-split-view-controls.js +14 -7
  6. package/lib/clay-split-view-input.js +5 -4
  7. package/lib/clay-split-view-render-proof.js +14 -4
  8. package/lib/clay-view-workspace-input.js +71 -0
  9. package/lib/project-admin-overview.js +22 -11
  10. package/lib/project-background-tasks.js +76 -0
  11. package/lib/project-connection.js +1 -1
  12. package/lib/project-file-history.js +11 -9
  13. package/lib/project-file-watch.js +26 -7
  14. package/lib/project-filesystem.js +5 -4
  15. package/lib/project-sessions.js +12 -5
  16. package/lib/project-shared-browser.js +7 -3
  17. package/lib/project-split-view-document.js +16 -2
  18. package/lib/project-split-views.js +27 -297
  19. package/lib/project-user-message.js +4 -3
  20. package/lib/project-vendor-login.js +2 -2
  21. package/lib/project-view-input.js +11 -0
  22. package/lib/project-view-pair.js +26 -0
  23. package/lib/project-view-store.js +348 -0
  24. package/lib/project-view-terminal-binding.js +16 -0
  25. package/lib/project-view-workspace.js +126 -0
  26. package/lib/project-worker-conversation.js +5 -0
  27. package/lib/project.js +1 -0
  28. package/lib/public/app.js +9 -0
  29. package/lib/public/css/contextual-clay.css +2 -1
  30. package/lib/public/css/issues.css +3 -0
  31. package/lib/public/css/mate-conversation.css +1 -1
  32. package/lib/public/css/pane.css +5 -1
  33. package/lib/public/css/pending-message-queue.css +24 -19
  34. package/lib/public/css/project-admin-overview.css +42 -13
  35. package/lib/public/css/right-workbench.css +0 -1
  36. package/lib/public/css/scheduled-tasks.css +7 -0
  37. package/lib/public/css/shared-browser.css +31 -8
  38. package/lib/public/css/split-view-layout.css +129 -10
  39. package/lib/public/css/tool-pane.css +124 -0
  40. package/lib/public/index.html +6 -6
  41. package/lib/public/modules/agent-message-bubbles.js +96 -0
  42. package/lib/public/modules/app-header.js +6 -0
  43. package/lib/public/modules/app-messages.js +26 -9
  44. package/lib/public/modules/background-task-previews.js +8 -0
  45. package/lib/public/modules/background-task-viewer.js +97 -0
  46. package/lib/public/modules/background-tasks-ui.js +34 -125
  47. package/lib/public/modules/chat-bubble-renderer.js +11 -5
  48. package/lib/public/modules/clay-file-links.js +2 -1
  49. package/lib/public/modules/clay-split-view-screen.js +6 -3
  50. package/lib/public/modules/clay-tool-pane-screen.js +35 -0
  51. package/lib/public/modules/contextual-clay-geometry.js +7 -3
  52. package/lib/public/modules/file-history-snapshot.js +12 -0
  53. package/lib/public/modules/file-pane-entrypoints.js +55 -0
  54. package/lib/public/modules/file-pane-history.js +78 -0
  55. package/lib/public/modules/file-pane-instances.js +140 -0
  56. package/lib/public/modules/file-pane-links.js +41 -0
  57. package/lib/public/modules/file-pane-render.js +48 -0
  58. package/lib/public/modules/file-pane-review.js +26 -0
  59. package/lib/public/modules/file-pane-toolbar.js +54 -0
  60. package/lib/public/modules/file-viewer-source.js +70 -0
  61. package/lib/public/modules/filebrowser.js +43 -82
  62. package/lib/public/modules/git-panel.js +5 -0
  63. package/lib/public/modules/issues.js +3 -4
  64. package/lib/public/modules/linear-panel.js +2 -2
  65. package/lib/public/modules/mate-knowledge-workbench.js +10 -6
  66. package/lib/public/modules/notifications.js +4 -1
  67. package/lib/public/modules/pane-activation.js +7 -3
  68. package/lib/public/modules/pane-binding.js +5 -1
  69. package/lib/public/modules/pane-browser-bridge.js +6 -2
  70. package/lib/public/modules/pane-browser-transport.js +110 -0
  71. package/lib/public/modules/pane-header-tooltips.js +32 -0
  72. package/lib/public/modules/pending-message-queue-drag.js +69 -0
  73. package/lib/public/modules/pending-message-queue.js +3 -33
  74. package/lib/public/modules/permission-control.js +6 -5
  75. package/lib/public/modules/plantuml-preview.js +12 -12
  76. package/lib/public/modules/project-admin-overview.js +125 -10
  77. package/lib/public/modules/project-grouping.js +9 -0
  78. package/lib/public/modules/right-workbench.js +34 -15
  79. package/lib/public/modules/scheduled-tasks.js +7 -6
  80. package/lib/public/modules/server-settings-controls.js +33 -4
  81. package/lib/public/modules/session-folder-toolbar.js +1 -1
  82. package/lib/public/modules/session-tool-item.js +86 -0
  83. package/lib/public/modules/settings-select.js +2 -1
  84. package/lib/public/modules/shared-browser-tabs.js +37 -21
  85. package/lib/public/modules/shared-browser.js +69 -9
  86. package/lib/public/modules/sidebar-projects.js +1 -10
  87. package/lib/public/modules/split-pane-renderer.js +1 -0
  88. package/lib/public/modules/split-session-empty-pane.js +47 -0
  89. package/lib/public/modules/split-view-create-menu.js +74 -65
  90. package/lib/public/modules/split-view-drag-preview.js +90 -0
  91. package/lib/public/modules/split-view-layout-client.js +1 -1
  92. package/lib/public/modules/split-view-layout-scope.js +4 -0
  93. package/lib/public/modules/split-view-picker-actions.js +71 -0
  94. package/lib/public/modules/split-view-picker-render.js +81 -0
  95. package/lib/public/modules/split-view-presentation-drag.js +36 -13
  96. package/lib/public/modules/split-view-presentation-drop.js +21 -18
  97. package/lib/public/modules/split-view-presentation-renderer.js +22 -17
  98. package/lib/public/modules/split-view-presentations.js +25 -33
  99. package/lib/public/modules/split-view-worker-activity.js +2 -2
  100. package/lib/public/modules/split-view-worker-transcript.js +70 -33
  101. package/lib/public/modules/split-view-workers.js +6 -4
  102. package/lib/public/modules/split-view.js +9 -2
  103. package/lib/public/modules/sticky-notes-browser.js +2 -2
  104. package/lib/public/modules/terminal-viewport.js +14 -0
  105. package/lib/public/modules/terminal.js +49 -23
  106. package/lib/public/modules/theme-palette.js +177 -0
  107. package/lib/public/modules/theme.js +2 -180
  108. package/lib/public/modules/tool-pane-chrome.js +88 -0
  109. package/lib/public/modules/tool-pane-drag.js +134 -0
  110. package/lib/public/modules/tool-pane-layout.js +95 -0
  111. package/lib/public/modules/tool-pane-legacy.js +60 -0
  112. package/lib/public/modules/tool-pane-tree.js +51 -0
  113. package/lib/public/modules/tools.js +5 -89
  114. package/lib/public/modules/tooltip.js +32 -6
  115. package/lib/public/modules/view-legacy-coordinates.js +16 -0
  116. package/lib/public/modules/view-mutations.js +24 -0
  117. package/lib/public/modules/view-pair-presentation.js +74 -0
  118. package/lib/public/modules/view-pair-validation.js +12 -0
  119. package/lib/public/modules/view-presentation-state.js +37 -0
  120. package/lib/public/modules/view-workspace-controller.js +210 -0
  121. package/lib/public/modules/view-workspace-state.js +103 -0
  122. package/lib/public/modules/view-workspace-validation.js +47 -0
  123. package/lib/public/modules/wireframe-workbench.js +13 -2
  124. package/lib/public/modules/workbench-layout-preference.js +12 -5
  125. package/lib/public/modules/workbench-panels.js +21 -0
  126. package/lib/public/modules/worker-message-previews.js +39 -0
  127. package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
  128. package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
  129. package/lib/public/pane-assets/chunk-6QFLYOVY.js +1 -0
  130. package/lib/public/pane-assets/chunk-7OVNC5HT.js +1 -0
  131. package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
  132. package/lib/public/pane-assets/chunk-EMLN2Y7L.js +1 -0
  133. package/lib/public/pane-assets/chunk-ENPAHXLB.js +1 -0
  134. package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
  135. package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
  136. package/lib/public/pane-assets/chunk-FQTNR4ZS.js +1 -0
  137. package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
  138. package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
  139. package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
  140. package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
  141. package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
  142. package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
  143. package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
  144. package/lib/public/pane-assets/chunk-R7QVVFUG.js +1 -0
  145. package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
  146. package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
  147. package/lib/public/pane-assets/chunk-SJOT74VW.js +2 -0
  148. package/lib/public/pane-assets/chunk-SJTGHIN5.js +1 -0
  149. package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
  150. package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
  151. package/lib/public/pane-assets/chunk-UPXU2IPC.js +1 -0
  152. package/lib/public/pane-assets/chunk-VEBYD7TL.js +1 -0
  153. package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
  154. package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
  155. package/lib/public/pane-assets/manifest.json +438 -381
  156. package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
  157. package/lib/public/style.css +1 -0
  158. package/lib/sdk-message-processor.js +5 -2
  159. package/lib/server-storage.js +27 -3
  160. package/lib/server-workbench-layout-settings.js +3 -3
  161. package/lib/server.js +4 -2
  162. package/lib/sessions.js +1 -1
  163. package/lib/split-view-layout.js +11 -7
  164. package/lib/storage-usage.js +29 -7
  165. package/lib/terminal-manager.js +2 -2
  166. package/lib/users-workbench-layout-preferences.js +14 -4
  167. package/lib/workbench-window-layout.js +30 -0
  168. package/lib/worker-message-previews.js +50 -0
  169. package/lib/ws-schema.js +1 -1
  170. package/lib/yoke/adapters/claude-worker.js +8 -0
  171. package/lib/yoke/adapters/claude.js +17 -2
  172. package/lib/yoke/claude-task-reader.js +94 -0
  173. package/lib/yoke/interface.js +1 -0
  174. package/package.json +1 -1
  175. package/lib/public/pane-assets/app-6H5S2Y3Z.js +0 -1
  176. package/lib/public/pane-assets/chunk-3QV5SDC4.js +0 -1
  177. package/lib/public/pane-assets/chunk-4FM3U3DW.js +0 -1
  178. package/lib/public/pane-assets/chunk-7GZH3FZY.js +0 -1
  179. package/lib/public/pane-assets/chunk-AGJTF5WQ.js +0 -1
  180. package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
  181. package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
  182. package/lib/public/pane-assets/chunk-NPKRTUNG.js +0 -2
  183. package/lib/public/pane-assets/chunk-VGA2Y3D2.js +0 -2
  184. package/lib/public/pane-assets/chunk-WKO3T75E.js +0 -167
  185. package/lib/public/pane-assets/style-TSRDU54Z.css +0 -1
@@ -0,0 +1,134 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { livePaneBinding, samePaneBinding } from './pane-binding.js';
4
+ import { insertWindow } from './tool-pane-tree.js';
5
+ import { resize } from './split-view-layout-client.js';
6
+ import { repaintToolPanes, saveToolPaneTree, toolPaneContext } from './tool-pane-layout.js';
7
+
8
+ function receiptCurrent(drag) {
9
+ var current = store.get('toolPaneWindowGeometry');
10
+ return current && current.key === toolPaneContext() && store.get('connected') && current.key === drag.receipt.key && current.socket === getWs() && current.socket === drag.receipt.socket &&
11
+ current.scope === drag.receipt.scope && current.scope === JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('currentProjectOwnerId'), store.get('projectSplitViewSocketGeneration')]) && current.revision === drag.receipt.revision &&
12
+ JSON.stringify(current.windows.map(function (entry) { return entry.id; }).sort()) === JSON.stringify(drag.windows.map(function (entry) { return entry.id; }).sort()) && JSON.stringify(current.base) === JSON.stringify(drag.receipt.base) &&
13
+ current.host.clientWidth === drag.width && current.host.clientHeight === drag.height &&
14
+ drag.windows.every(function (entry) {
15
+ if (!entry.element.isConnected || entry.element.classList.contains('hidden')) return false;
16
+ if (!entry.frame) return entry.element.dataset.toolWindowId === entry.id;
17
+ var live = entry.frame.clayPane && entry.frame.clayPane.identity();
18
+ return entry.frame.contentDocument === entry.document && entry.frame.contentWindow === entry.window &&
19
+ live && samePaneBinding(live.binding, entry.binding) && livePaneBinding(entry.frame);
20
+ });
21
+ }
22
+ function capture(event, source, divider) {
23
+ var receipt = store.get('toolPaneWindowGeometry'), preference = store.get('workbenchLayoutPreference') || {};
24
+ if (!receipt || (!store.get('projectSplitViewActiveId') && (!preference.ready || preference.loading || preference.saving)) || store.get('projectSplitViewDrag') || store.get('projectSplitViewPending')) return null;
25
+ var drag = { pointerId: event.pointerId, receipt: receipt, source: source, divider: divider, startX: event.clientX, startY: event.clientY,
26
+ width: receipt.host.clientWidth, height: receipt.host.clientHeight, tree: receipt.tree, moved: false, windows: receipt.windows.map(function (item) {
27
+ var live = item.frame && item.frame.clayPane && item.frame.clayPane.identity();
28
+ return Object.assign({}, item, { rect: item.element.getBoundingClientRect(), document: item.frame && item.frame.contentDocument,
29
+ window: item.frame && item.frame.contentWindow, binding: live && live.binding, pointerEvents: item.frame && item.frame.style.pointerEvents });
30
+ }) };
31
+ return receiptCurrent(drag) ? drag : null;
32
+ }
33
+ function begin(drag) {
34
+ drag.moved = true;
35
+ drag.windows.forEach(function (item) { if (item.frame) item.frame.style.pointerEvents = 'none'; });
36
+ if (drag.source) drag.source.element.classList.add('tool-pane-drag-source');
37
+ document.body.classList.add('tool-pane-dragging'); drag.receipt.host.classList.add('split-view-drag-preview');
38
+ try { document.documentElement.setPointerCapture(drag.pointerId); } catch (error) {}
39
+ }
40
+ function pointerDown(event) {
41
+ if (event.button !== 0 || event.isPrimary === false || store.get('toolPaneDrag')) return;
42
+ var header = event.target.closest && event.target.closest('.tool-pane-window > .tool-pane-header, #split-host > .split-pane > .split-pane-header');
43
+ if (!header || event.target.closest('button, input, a, .tool-pane-title, .tool-pane-kind, .split-pane-title, .split-view-runtime, .split-worker-runtime')) return;
44
+ var receipt = store.get('toolPaneWindowGeometry');
45
+ if (!receipt || !receipt.windows.some(function (item) { return !!item.tool; })) return;
46
+ var source = receipt.windows.find(function (item) { return item.element === header.parentNode; });
47
+ var drag = source && capture(event, source);
48
+ if (!drag) return;
49
+ store.set({ toolPaneDrag: drag }); event.preventDefault(); event.stopImmediatePropagation();
50
+ }
51
+ function preview(drag, tree) {
52
+ drag.tree = tree;
53
+ store.set({ toolPaneLayoutPreview: { key: drag.receipt.key, base: drag.receipt.base, tree: tree, socket: drag.receipt.socket } });
54
+ repaintToolPanes();
55
+ }
56
+ function move(event) {
57
+ var drag = store.get('toolPaneDrag');
58
+ if (!drag || drag.pointerId !== event.pointerId) return;
59
+ if (!receiptCurrent(drag)) { finish(false); return; }
60
+ if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 6) return;
61
+ if (!drag.moved) begin(drag);
62
+ event.preventDefault();
63
+ if (drag.divider) {
64
+ var entry = drag.divider, horizontal = entry.axis === 'horizontal';
65
+ var delta = horizontal ? event.clientX - drag.startX : event.clientY - drag.startY;
66
+ var next = resize(drag.receipt.tree, entry.path, Math.max(.05, Math.min(.95, entry.ratio + delta / Math.max(1, entry.available))));
67
+ if (next) preview(drag, next);
68
+ return;
69
+ }
70
+ var target = drag.windows.find(function (entry) { var r = entry.rect; return event.clientX >= r.left && event.clientX <= r.right && event.clientY >= r.top && event.clientY <= r.bottom; });
71
+ var nextTree = drag.receipt.tree;
72
+ if (target && target.id !== drag.source.id) {
73
+ var r = target.rect, x = (event.clientX - r.left) / r.width, y = (event.clientY - r.top) / r.height;
74
+ var edge = Math.min(x, 1 - x, y, 1 - y), side = 'center';
75
+ if (edge < .25) side = edge === x ? 'left' : edge === 1 - x ? 'right' : edge === y ? 'top' : 'bottom';
76
+ nextTree = insertWindow(drag.receipt.tree, drag.source.id, target.id, side);
77
+ }
78
+ preview(drag, nextTree);
79
+ var ghost = document.getElementById('tool-pane-drag-ghost');
80
+ if (!ghost) { ghost = document.createElement('div'); ghost.id = 'tool-pane-drag-ghost'; ghost.className = 'split-view-drag-ghost'; document.body.appendChild(ghost); }
81
+ ghost.textContent = drag.source.element.querySelector('.tool-pane-title, .split-pane-title').textContent;
82
+ ghost.style.left = event.clientX + 12 + 'px'; ghost.style.top = event.clientY + 12 + 'px'; ghost.hidden = false;
83
+ }
84
+ async function finish(commit) {
85
+ var drag = store.get('toolPaneDrag');
86
+ if (!drag) return;
87
+ var allowed = commit && drag.moved && receiptCurrent(drag) && JSON.stringify(drag.tree) !== JSON.stringify(drag.receipt.tree);
88
+ store.set({ toolPaneDrag: null });
89
+ drag.windows.forEach(function (entry) { if (entry.frame && entry.frame.isConnected) entry.frame.style.pointerEvents = entry.pointerEvents; });
90
+ if (drag.source) drag.source.element.classList.remove('tool-pane-drag-source');
91
+ document.body.classList.remove('tool-pane-dragging');
92
+ var ghost = document.getElementById('tool-pane-drag-ghost'); if (ghost) ghost.hidden = true;
93
+ try { if (document.documentElement.hasPointerCapture(drag.pointerId)) document.documentElement.releasePointerCapture(drag.pointerId); } catch (error) {}
94
+ var saved = allowed ? await saveToolPaneTree(drag.receipt, drag.tree) : false;
95
+ if (!store.get('toolPaneDrag')) { store.set({ toolPaneLayoutPreview: null }); repaintToolPanes(); }
96
+ setTimeout(function () { if (!store.get('toolPaneDrag')) drag.receipt.host.classList.remove('split-view-drag-preview'); }, 320);
97
+ return saved;
98
+ }
99
+ function up(event) {
100
+ var drag = store.get('toolPaneDrag');
101
+ if (!drag || drag.pointerId !== event.pointerId) return;
102
+ if (drag.moved) move(event);
103
+ finish(true);
104
+ }
105
+ export function bindToolPaneDivider(divider, entry, tree) {
106
+ divider.addEventListener('pointerdown', function (event) {
107
+ if (event.button !== 0 || store.get('toolPaneDrag')) return;
108
+ var drag = capture(event, null, entry);
109
+ if (!drag) return;
110
+ store.set({ toolPaneDrag: drag }); begin(drag); event.preventDefault(); event.stopImmediatePropagation();
111
+ });
112
+ divider.addEventListener('keydown', function (event) {
113
+ var direction = ['ArrowLeft', 'ArrowUp'].indexOf(event.key) >= 0 ? -1 : ['ArrowRight', 'ArrowDown'].indexOf(event.key) >= 0 ? 1 : 0;
114
+ if (!direction) return;
115
+ var receipt = store.get('toolPaneWindowGeometry'), next = resize(tree, entry.path, Math.max(.05, Math.min(.95, entry.ratio + direction * .03)));
116
+ if (receipt && next) { event.preventDefault(); saveToolPaneTree(receipt, next); }
117
+ });
118
+ }
119
+ export function initToolPaneDrag() {
120
+ if (store.get('toolPaneDragInitialized')) return;
121
+ store.set({ toolPaneDragInitialized: true });
122
+ document.addEventListener('pointerdown', pointerDown, true);
123
+ document.addEventListener('pointermove', move, true); document.addEventListener('pointerup', up, true);
124
+ document.addEventListener('pointercancel', function () { finish(false); }, true);
125
+ document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && store.get('toolPaneDrag')) { event.preventDefault(); finish(false); } }, true);
126
+ window.addEventListener('blur', function () { finish(false); });
127
+ }
128
+
129
+ store.subscribe(function (state, previous) {
130
+ if (state.toolPaneDrag && (state.myUserId !== previous.myUserId || state.currentSlug !== previous.currentSlug ||
131
+ state.currentProjectOwnerId !== previous.currentProjectOwnerId || state.splitPanes !== previous.splitPanes ||
132
+ state.connected !== previous.connected || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId ||
133
+ state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration)) finish(false);
134
+ });
@@ -0,0 +1,95 @@
1
+ import { boundLegacyCoordinates } from './view-legacy-coordinates.js';
2
+ import { pairDisplayTree, pairBase } from './view-pair-presentation.js';
3
+ import { activeSavedView, displayTree, fromDisplayTree, copy, workspaceKey } from './view-workspace-state.js';
4
+ import { persistWorkspace } from './view-workspace-controller.js';
5
+ import { store } from './store.js';
6
+ import { getWs } from './ws-ref.js';
7
+ import { TOOL_PANES, toolWindowId, workspaceWindowTree } from './tool-pane-tree.js';
8
+ import { renderPresentationSplitView } from './split-view-presentations.js';
9
+ import { ensureWorkbenchLayoutPreference } from './workbench-layout-preference.js';
10
+ import { showToast } from './utils.js';
11
+ import { initToolPaneDrag, bindToolPaneDivider } from './tool-pane-drag.js';
12
+
13
+ export function toolPaneContext() {
14
+ var state = store.snap(), view = (state.projectSplitViews || []).find(function (item) { return item.id === state.projectSplitViewActiveId; });
15
+ var split = state.splitPanes;
16
+ var origin = view ? view.id : split && split.panes ? 'legacy-' + (split.groupId || split.panes.map(function (pane) { return pane.sessionId; }).join('-')) : state.projectSplitViewSingletonOrigin;
17
+ return origin && state.currentSlug && JSON.stringify([state.currentSlug, state.currentProjectOwnerId || null, origin]);
18
+ }
19
+ export function dockedToolPanes() {
20
+ if (window.innerWidth < 1024 || store.get('paneMode')) return [];
21
+ return (store.get('rightWorkbenchDockedOwners') || []).filter(function (name) {
22
+ var definition = TOOL_PANES[name], panel = definition && document.getElementById(definition.id);
23
+ return panel && !panel.classList.contains('hidden') && !panel.classList.contains('panel-fullscreen');
24
+ });
25
+ }
26
+ export function toolPaneLayoutTree(view, base) {
27
+ initToolPaneDrag();
28
+ var owners = dockedToolPanes(), key = toolPaneContext(), preference = store.get('workbenchLayoutPreference') || {};
29
+ var draft = store.get('toolPaneLayoutPreview'), saved = preference.layouts && preference.layouts[key];
30
+ if (view && view.workspace) {
31
+ var legacy = store.get('savedWorkspaceLegacyLayout');
32
+ saved = legacy && legacy.key === workspaceKey(view) ? boundLegacyCoordinates(legacy.record, view.workspace) : { base: view.workspace.linkedPair ? pairBase(view.workspace.linkedPair, view.tree) : view.tree, tree: pairDisplayTree(view.workspace) };
33
+ }
34
+ if (draft && draft.key === key && JSON.stringify(draft.base) === JSON.stringify(base) && draft.socket === getWs()) return draft.tree;
35
+ return owners.length || saved ? workspaceWindowTree(base, saved, owners) : base;
36
+ }
37
+ export function paintToolPaneWindows(host, view, base, tree, geometry, inset) {
38
+ var owners = dockedToolPanes(), key = toolPaneContext(), main = document.getElementById('main-panels');
39
+ if (!main) return;
40
+ main.classList.toggle('view-session-focused', !!store.get('projectSplitViewFocusedOrigin'));
41
+ var hostRect = host.getBoundingClientRect(), mainRect = main.getBoundingClientRect();
42
+ var windows = Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)')).map(function (pane) {
43
+ var frame = pane.querySelector('.split-pane-frame'); return { id: frame && frame.dataset.sessionOriginId, element: pane, frame: frame };
44
+ });
45
+ Object.keys(TOOL_PANES).forEach(function (name) {
46
+ var panel = document.getElementById(TOOL_PANES[name].id), rect = geometry.panes[toolWindowId(name)];
47
+ if (!panel) return;
48
+ if (!panel.dataset.toolPaneFullscreenObserved) {
49
+ panel.dataset.toolPaneFullscreenObserved = 'true';
50
+ var observer = new MutationObserver(function (records) {
51
+ if (records.some(function (record) { return (String(record.oldValue || '').split(/\s+/).indexOf('panel-fullscreen') >= 0) !== panel.classList.contains('panel-fullscreen'); })) requestAnimationFrame(repaintToolPanes);
52
+ });
53
+ observer.observe(panel, { attributes: true, attributeOldValue: true, attributeFilter: ['class'] });
54
+ panel.addEventListener('clay-tool-pane-dispose', function () { observer.disconnect(); }, { once: true });
55
+ }
56
+ var mounted = owners.indexOf(name) >= 0 && !!rect;
57
+ var wasMounted = panel.classList.contains('tool-pane-window');
58
+ panel.classList.toggle('tool-pane-window', mounted);
59
+ if (!mounted) {
60
+ if (wasMounted) ['left', 'top', 'width', 'height', 'right', 'maxHeight'].forEach(function (field) { panel.style[field] = ''; });
61
+ delete panel.dataset.toolWindowId; return;
62
+ }
63
+ panel.dataset.toolWindowId = toolWindowId(name);
64
+ panel.style.left = hostRect.left - mainRect.left + inset.left + rect.x + 'px';
65
+ panel.style.top = hostRect.top - mainRect.top + inset.top + rect.y + 'px';
66
+ panel.style.width = rect.width + 'px'; panel.style.height = rect.height + 'px'; panel.style.right = 'auto'; panel.style.maxHeight = 'none';
67
+ windows.push({ id: toolWindowId(name), element: panel, tool: name });
68
+ });
69
+ main.classList.toggle('has-tool-pane-windows', owners.length > 0);
70
+ if (Math.abs(host.clientWidth - hostRect.width) > 1) requestAnimationFrame(repaintToolPanes);
71
+ store.set({ toolPaneWindowGeometry: { key: key, base: base, tree: tree, view: view, windows: windows,
72
+ host: host, socket: getWs(), revision: view && view.workspace ? view.revision : (store.get('workbenchLayoutPreference') || {}).revision,
73
+ scope: JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('currentProjectOwnerId'), store.get('projectSplitViewSocketGeneration')]) } });
74
+ }
75
+ export function bindWorkspaceDivider(divider, entry, tree) { bindToolPaneDivider(divider, entry, tree); }
76
+ export function repaintToolPanes() { renderPresentationSplitView(); }
77
+ export async function saveToolPaneTree(receipt, tree) {
78
+ var live = store.get('toolPaneWindowGeometry');
79
+ if (!live || live.key !== toolPaneContext() || live.key !== receipt.key || live.socket !== receipt.socket || live.scope !== receipt.scope || live.revision !== receipt.revision || !store.get('connected')) return false;
80
+ var view = activeSavedView();
81
+ if (view) { var value = copy(view.workspace); value.tree = fromDisplayTree(value, tree); if (value.linkedPair) value.linkedPair.tree = copy(tree); return persistWorkspace(value); }
82
+ showToast('Wait for the saved View before changing pane positions.', 'warn');
83
+ return false;
84
+ }
85
+ store.subscribe(function (state, previous) {
86
+ if (state.paneMode) return;
87
+ var changed = state.rightWorkbenchDockedOwners !== previous.rightWorkbenchDockedOwners || state.workbenchLayoutPreference !== previous.workbenchLayoutPreference ||
88
+ state.toolPaneLayoutPreview !== previous.toolPaneLayoutPreview;
89
+ if (!changed) return;
90
+ if (!state.toolPaneRepaintQueued) {
91
+ store.set({ toolPaneRepaintQueued: true });
92
+ requestAnimationFrame(function () { store.set({ toolPaneRepaintQueued: false }); renderPresentationSplitView(); });
93
+ }
94
+ if ((state.rightWorkbenchDockedOwners || []).some(function (name) { return !!TOOL_PANES[name]; }) && !(state.workbenchLayoutPreference || {}).ready && !(state.workbenchLayoutPreference || {}).loading && !(state.workbenchLayoutPreference || {}).saving && !(state.workbenchLayoutPreference || {}).error) ensureWorkbenchLayoutPreference();
95
+ });
@@ -0,0 +1,60 @@
1
+ import { ensureArrangeTrigger } from './split-view-presentation-renderer.js';
2
+ import { pairBase } from './view-pair-presentation.js';
3
+ import { activeSavedView } from './view-workspace-state.js';
4
+ // Legacy Driver/Worker pairs use the same display-only window geometry.
5
+ import { store } from './store.js';
6
+ import { rectangles } from './split-view-layout-client.js';
7
+ import { toolPaneLayoutTree, paintToolPaneWindows, bindWorkspaceDivider, dockedToolPanes } from './tool-pane-layout.js';
8
+
9
+ export function clearToolPaneWindows() {
10
+ document.querySelectorAll('.tool-pane-window').forEach(function (panel) {
11
+ panel.classList.remove('tool-pane-window'); delete panel.dataset.toolWindowId;
12
+ ['left', 'top', 'width', 'height', 'right', 'maxHeight'].forEach(function (field) { panel.style[field] = ''; });
13
+ });
14
+ var main = document.getElementById('main-panels');
15
+ if (main) main.classList.remove('has-tool-pane-windows');
16
+ store.set({ toolPaneWindowGeometry: null });
17
+ }
18
+ export function paintLegacyToolPanes(host) {
19
+ var split = store.get('splitPanes');
20
+ if (!host || !split || !split.panes || split.panes.length < 2) return false;
21
+ var panes = Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)'));
22
+ var ids = panes.map(function (pane) {
23
+ var frame = pane.querySelector('.split-pane-frame');
24
+ if (frame && !frame.dataset.toolPaneGeometryReadyBound) {
25
+ frame.dataset.toolPaneGeometryReadyBound = 'true';
26
+ frame.addEventListener('clay-pane-ready', function () { queueMicrotask(function () { if (frame.isConnected) paintLegacyToolPanes(host); }); });
27
+ }
28
+ return frame && frame.dataset.sessionOriginId;
29
+ });
30
+ if (ids.length < split.panes.length || ids.some(function (id) { return !id; }) || new Set(ids).size !== ids.length) return false;
31
+ var base = { id: ids[0] };
32
+ ids.slice(1).forEach(function (id) { base = { axis: 'horizontal', ratio: .5, left: base, right: { id: id } }; });
33
+ var view = activeSavedView() || { id: 'legacy-' + (split.groupId || ids.join('-')), activeSessionOriginId: ids[0] };
34
+ if (view.workspace && view.workspace.linkedPair) base = pairBase(view.workspace.linkedPair, view.tree);
35
+ host.classList.add('split-view-canvas'); host.dataset.positioned = 'true'; delete host.dataset.singlePane;
36
+ var style = getComputedStyle(host), left = parseFloat(style.paddingLeft) || 0, top = parseFloat(style.paddingTop) || 0;
37
+ var width = host.clientWidth - left - (parseFloat(style.paddingRight) || 0), height = host.clientHeight - top - (parseFloat(style.paddingBottom) || 0);
38
+ var tree = toolPaneLayoutTree(view, base), geometry = rectangles(tree, Math.max(0, width), Math.max(0, height), 7);
39
+ paintToolPaneWindows(host, view, base, tree, geometry, { left: left, top: top });
40
+ var focused = store.get('projectSplitViewFocusedOrigin');
41
+ panes.forEach(function (pane, index) {
42
+ var rect = geometry.panes[ids[index]];
43
+ pane.hidden = !!focused && ids[index] !== focused; pane.style.display = pane.hidden ? 'none' : '';
44
+ if (focused === ids[index]) rect = { x: 0, y: 0, width: width, height: height };
45
+ if (!rect) return;
46
+ if (view.sessions && view.sessions.some(function (s) { return s.sessionOriginId === ids[index]; })) ensureArrangeTrigger(pane.querySelector('.split-pane-header'));
47
+ pane.style.left = left + rect.x + 'px'; pane.style.top = top + rect.y + 'px';
48
+ pane.style.width = rect.width + 'px'; pane.style.height = rect.height + 'px';
49
+ });
50
+ host.querySelectorAll(':scope > .split-view-divider').forEach(function (divider) { divider.remove(); });
51
+ (focused ? [] : geometry.dividers).forEach(function (entry) {
52
+ var divider = document.createElement('div'); divider.className = 'split-view-divider'; divider.dataset.axis = entry.axis;
53
+ divider.setAttribute('role', 'separator'); divider.setAttribute('tabindex', '0'); divider.setAttribute('aria-label', 'Resize pane windows');
54
+ divider.setAttribute('aria-orientation', entry.axis === 'horizontal' ? 'vertical' : 'horizontal');
55
+ divider.style.left = left + entry.rect.x + 'px'; divider.style.top = top + entry.rect.y + 'px';
56
+ divider.style.width = entry.rect.width + 'px'; divider.style.height = entry.rect.height + 'px';
57
+ bindWorkspaceDivider(divider, entry, tree); host.appendChild(divider);
58
+ });
59
+ return true;
60
+ }
@@ -0,0 +1,51 @@
1
+ import { WORKBENCH_PANELS } from './workbench-panels.js';
2
+ import { listIds, remove, pairTree, swap } from './split-view-layout-client.js';
3
+
4
+ export var TOOL_PANES = { terminal: { id: 'terminal-container', label: 'Terminal', icon: 'terminal' },
5
+ browser: { id: 'shared-browser-panel', label: 'Browser', icon: 'globe' }, 'file-viewer': { id: 'file-viewer', label: 'Files', icon: 'file-text' } };
6
+ Object.assign(TOOL_PANES, WORKBENCH_PANELS);
7
+ export function toolWindowId(name) { return 'tool-' + name; }
8
+ export function isToolWindow(id) { return /^tool-file-[A-Za-z0-9_-]+$/.test(id) || Object.keys(TOOL_PANES).some(function (name) { return toolWindowId(name) === id; }); }
9
+ export function insertWindow(tree, source, target, side, ratio) {
10
+ if (source === target) return tree;
11
+ if (side === 'center' && isToolWindow(source) && isToolWindow(target)) return swap(tree, source, target).tree;
12
+ side = side === 'center' ? 'right' : side;
13
+ var base = remove(tree, source), found = false;
14
+ function walk(node) {
15
+ if (!node) return node;
16
+ if (node.id) {
17
+ if (node.id !== target) return node;
18
+ found = true;
19
+ var result = pairTree(target, source, side);
20
+ result.ratio = ratio || .5;
21
+ return result;
22
+ }
23
+ return { axis: node.axis, ratio: node.ratio, left: walk(node.left), right: walk(node.right) };
24
+ }
25
+ var next = walk(base);
26
+ return found && listIds(next).length <= 24 ? next : tree;
27
+ }
28
+ function stripUnknown(tree, allowed) {
29
+ var result = tree;
30
+ listIds(tree).forEach(function (id) { if (allowed.indexOf(id) < 0) result = remove(result, id); });
31
+ return result;
32
+ }
33
+ function placements(tree, output) {
34
+ if (!tree || tree.id) return;
35
+ var left = listIds(tree.left), right = listIds(tree.right);
36
+ if (tree.left.id && isToolWindow(tree.left.id)) output.push({ id: tree.left.id, target: right[0], side: tree.axis === 'horizontal' ? 'left' : 'top', ratio: tree.ratio });
37
+ else if (tree.right.id && isToolWindow(tree.right.id)) output.push({ id: tree.right.id, target: left[0], side: tree.axis === 'horizontal' ? 'right' : 'bottom', ratio: tree.ratio });
38
+ placements(tree.left, output); placements(tree.right, output);
39
+ }
40
+ export function workspaceWindowTree(base, saved, owners) {
41
+ var sessions = listIds(base), tools = owners.map(toolWindowId), allowed = sessions.concat(tools), tree = base;
42
+ if (saved && saved.tree && JSON.stringify(saved.base) === JSON.stringify(base)) tree = stripUnknown(saved.tree, allowed);
43
+ else if (saved && saved.tree) {
44
+ var attached = []; placements(saved.tree, attached);
45
+ attached.reverse().forEach(function (item) { if (tools.indexOf(item.id) >= 0 && listIds(tree).indexOf(item.target) >= 0) tree = insertWindow(tree, item.id, item.target, item.side, item.ratio); });
46
+ }
47
+ tools.forEach(function (id) {
48
+ if (listIds(tree).indexOf(id) < 0) tree = tree ? { axis: 'horizontal', ratio: .66, left: tree, right: { id: id } } : { id: id };
49
+ });
50
+ return tree;
51
+ }
@@ -1,5 +1,5 @@
1
+ import { createSessionToolItem, createSessionToolGroup, sessionToolGroupSummary } from './session-tool-item.js';
1
2
  import { renderProductReview } from './product-review.js';
2
- import { clayToolGroupSummary } from './clay-tool-presentation.js';
3
3
  import { applyClayToolItem, applyClayToolResult, finishClayToolItem, findEndedClayTool, clayToolResultGroup, bindToolGroupDisclosure, syncToolGroupDisclosure, syncClayToolGroup } from './clay-tool-activity.js';
4
4
  import { rememberMountedToolGroup, mountedToolGroup, transcriptToolGroups, popupChatRenderingEnabled, popupChatRenderingActive, syncPopupActivityGroup, syncPopupChatToolGroups as syncPopupGroups } from './popup-chat-rendering.js';
5
5
  import { escapeHtml, copyToClipboard } from './utils.js';
@@ -80,61 +80,12 @@ function findToolGroup(groupId) {
80
80
  return toolGroups[groupId] || null;
81
81
  }
82
82
 
83
- function toolGroupSummary(group) {
84
- var claySummary = clayToolGroupSummary(group, tools);
85
- if (claySummary) return claySummary;
86
- var names = group.toolNames;
87
- var count = names.length;
88
- var allDone = group.doneCount >= count;
89
-
90
- // Count by tool name
91
- var counts = {};
92
- for (var i = 0; i < names.length; i++) {
93
- counts[names[i]] = (counts[names[i]] || 0) + 1;
94
- }
95
- var uniqueNames = Object.keys(counts);
96
-
97
- if (uniqueNames.length === 1) {
98
- var name = uniqueNames[0];
99
- var n = counts[name];
100
- if (allDone) {
101
- switch (name) {
102
- case "Read": return "Read " + n + " file" + (n > 1 ? "s" : "");
103
- case "Edit": return "Edited " + n + " file" + (n > 1 ? "s" : "");
104
- case "Write": return "Wrote " + n + " file" + (n > 1 ? "s" : "");
105
- case "Bash": return "Ran " + n + " command" + (n > 1 ? "s" : "");
106
- case "Grep": return "Searched " + n + " pattern" + (n > 1 ? "s" : "");
107
- case "Glob": return "Found " + n + " pattern" + (n > 1 ? "s" : "");
108
- case "Task": return "Ran " + n + " task" + (n > 1 ? "s" : "");
109
- case "WebSearch": return "Searched " + n + " quer" + (n > 1 ? "ies" : "y");
110
- case "WebFetch": return "Fetched " + n + " URL" + (n > 1 ? "s" : "");
111
- default: return "Ran " + n + " tool" + (n > 1 ? "s" : "");
112
- }
113
- }
114
- switch (name) {
115
- case "Read": return "Reading " + n + " file" + (n > 1 ? "s" : "") + "...";
116
- case "Edit": return "Editing " + n + " file" + (n > 1 ? "s" : "") + "...";
117
- case "Write": return "Writing " + n + " file" + (n > 1 ? "s" : "") + "...";
118
- case "Bash": return "Running " + n + " command" + (n > 1 ? "s" : "") + "...";
119
- case "Grep": return "Searching " + n + " pattern" + (n > 1 ? "s" : "") + "...";
120
- case "Glob": return "Finding " + n + " pattern" + (n > 1 ? "s" : "") + "...";
121
- case "Task": return "Running " + n + " task" + (n > 1 ? "s" : "") + "...";
122
- case "WebSearch": return "Searching " + n + " quer" + (n > 1 ? "ies" : "y") + "...";
123
- case "WebFetch": return "Fetching " + n + " URL" + (n > 1 ? "s" : "") + "...";
124
- default: return "Running " + n + " tool" + (n > 1 ? "s" : "") + "...";
125
- }
126
- }
127
-
128
- // Mixed tools
129
- if (allDone) return "Ran " + count + " tools";
130
- return "Running " + count + " tools...";
131
- }
132
83
 
133
84
  function updateToolGroupHeader(group) {
134
85
  if (!group || !group.el) return;
135
86
  var popupChat = group.popupChat && popupChatRenderingActive();
136
87
  var label = group.el.querySelector(".tool-group-label");
137
- var summary = toolGroupSummary(group);
88
+ var summary = sessionToolGroupSummary(group, tools);
138
89
  if (label && !popupChat) label.textContent = summary;
139
90
 
140
91
  var allDone = group.doneCount >= group.toolCount;
@@ -1644,31 +1595,12 @@ export function createToolItem(id, name) {
1644
1595
  // Group management: create new group or reuse existing open group
1645
1596
  if (!currentToolGroup || currentToolGroup.closed) {
1646
1597
  toolGroupCounter++;
1647
- var groupEl = document.createElement("div");
1648
- groupEl.className = "tool-group";
1649
1598
  var identity = resolveProjectAssistantIdentity();
1650
1599
  var isMateToolGroup = identity.kind === 'mate';
1651
- if (isMateToolGroup) groupEl.classList.add("mate-tool-group");
1600
+ var groupEl = createSessionToolGroup(isMateToolGroup ? identity.avatarUrl : null);
1601
+ if (isMateToolGroup) groupEl.classList.add('mate-tool-group');
1652
1602
  groupEl.dataset.groupId = "g" + toolGroupCounter;
1653
1603
 
1654
- var toolGroupInner =
1655
- '<div class="tool-group-header" style="display:none">' +
1656
- '<span class="tool-group-chevron">' + iconHtml("chevron-right") + '</span>' +
1657
- '<span class="tool-group-bullet"></span>' +
1658
- '<span class="tool-group-label">Running...</span>' +
1659
- '<span class="tool-group-status-icon">' + iconHtml("loader", "icon-spin") + '</span>' +
1660
- '</div>' +
1661
- '<div class="tool-group-items"></div>';
1662
-
1663
- if (isMateToolGroup) {
1664
- var mateAvatar = identity.avatarUrl;
1665
- groupEl.innerHTML =
1666
- '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
1667
- '<div class="dm-bubble-content">' + toolGroupInner + '</div>';
1668
- } else {
1669
- groupEl.innerHTML = toolGroupInner;
1670
- }
1671
-
1672
1604
  applyProjectAssistantIdentity(groupEl, identity);
1673
1605
  bindToolGroupDisclosure(groupEl, function () {
1674
1606
  var group = mountedToolGroup(groupEl);
@@ -1694,23 +1626,7 @@ export function createToolItem(id, name) {
1694
1626
  rememberMountedToolGroup(currentToolGroup);
1695
1627
  }
1696
1628
 
1697
- var el = document.createElement("div");
1698
- el.className = "tool-item";
1699
- el.dataset.toolId = id;
1700
- el.innerHTML =
1701
- '<div class="tool-header">' +
1702
- '<span class="tool-chevron">' + iconHtml("chevron-right") + '</span>' +
1703
- '<span class="tool-bullet"></span>' +
1704
- '<span class="tool-name"></span>' +
1705
- '<span class="tool-desc"></span>' +
1706
- '<span class="tool-status-icon">' + iconHtml("loader", "icon-spin") + '</span>' +
1707
- '</div>' +
1708
- '<div class="tool-subtitle">' +
1709
- '<span class="tool-connector">&#9492;</span>' +
1710
- '<span class="tool-subtitle-text">Running...</span>' +
1711
- '</div>';
1712
-
1713
- el.querySelector(".tool-name").textContent = name;
1629
+ var el = createSessionToolItem(id, name);
1714
1630
 
1715
1631
  tools[id] = { el: el, name: name, input: null, done: false, groupId: currentToolGroup.id };
1716
1632
  applyClayToolItem(tools[id]);
@@ -1,3 +1,5 @@
1
+ import { initPaneHeaderTooltips } from './pane-header-tooltips.js';
2
+
1
3
  // --- Global JS tooltip system ---
2
4
  // Unified tooltip using [data-tip] attribute.
3
5
  // Usage: initTooltips() auto-binds, converts [title] to [data-tip].
@@ -9,13 +11,15 @@ var SHOW_DELAY = 120;
9
11
  var currentTarget = null;
10
12
 
11
13
  function initTooltips() {
14
+ if (tooltipEl) return;
12
15
  // Create singleton tooltip element
13
16
  tooltipEl = document.createElement("div");
14
- tooltipEl.className = "tooltip";
17
+ tooltipEl.className = "tooltip"; tooltipEl.id = "clay-tooltip"; tooltipEl.setAttribute("role", "tooltip"); tooltipEl.setAttribute("aria-hidden", "true");
15
18
  document.body.appendChild(tooltipEl);
16
19
 
17
20
  // Convert existing title attributes to data-tip in target areas
18
21
  convertTitles();
22
+ initPaneHeaderTooltips();
19
23
 
20
24
  // Delegate hover events on document for [data-tip]
21
25
  document.addEventListener("mouseover", function (e) {
@@ -34,6 +38,13 @@ function initTooltips() {
34
38
  hideTooltip();
35
39
  });
36
40
 
41
+ document.addEventListener("focusin", function (event) {
42
+ var target = event.target.closest("[data-tip], [data-tip-html]");
43
+ if (target) scheduleShow(target);
44
+ });
45
+ document.addEventListener("focusout", function () { cancelShow(); hideTooltip(); });
46
+ document.addEventListener("keydown", function (event) { if (event.key === "Escape") { cancelShow(); hideTooltip(); } });
47
+ window.addEventListener("blur", function () { cancelShow(); hideTooltip(); });
37
48
  document.addEventListener("pointerdown", function () {
38
49
  cancelShow();
39
50
  hideTooltip();
@@ -58,8 +69,8 @@ function convertTitles() {
58
69
  var el = els[i];
59
70
  if (!el.getAttribute("data-tip")) {
60
71
  el.setAttribute("data-tip", el.getAttribute("title"));
61
- el.removeAttribute("title");
62
72
  }
73
+ el.removeAttribute("title");
63
74
  }
64
75
  }
65
76
 
@@ -83,17 +94,23 @@ function cancelShow() {
83
94
  }
84
95
 
85
96
  function showTooltipAt(target) {
86
- if (!tooltipEl) return;
97
+ if (!tooltipEl || !target.isConnected || !target.getClientRects().length) return;
98
+ // A message preview already describes this Worker/task tab.
99
+ if (target.getAttribute('aria-describedby') === 'agent-message-bubble' && document.getElementById('agent-message-bubble')) return;
87
100
  // Suppress tooltip when an associated popover/picker is open
88
101
  var suppressSel = target.getAttribute("data-tip-suppress-when-open");
89
102
  if (suppressSel) {
90
103
  var openEl = document.querySelector(suppressSel);
91
- if (openEl && !openEl.classList.contains("hidden")) return;
104
+ if (openEl && !openEl.hidden && !openEl.classList.contains("hidden")) return;
92
105
  }
93
106
  var html = target.getAttribute("data-tip-html");
94
107
  var text = target.getAttribute("data-tip");
95
108
  if (!html && !text) return;
109
+ if (currentTarget && currentTarget !== target) hideTooltip();
96
110
  currentTarget = target;
111
+ var described = (target.getAttribute('aria-describedby') || '').split(/\s+/).filter(Boolean);
112
+ if (described.indexOf('clay-tooltip') < 0) described.push('clay-tooltip');
113
+ target.setAttribute('aria-describedby', described.join(' '));
97
114
 
98
115
  if (html) {
99
116
  tooltipEl.innerHTML = html;
@@ -113,6 +130,7 @@ function showTooltipAt(target) {
113
130
  tooltipEl.style.top = "-9999px";
114
131
  tooltipEl.style.left = "0";
115
132
  tooltipEl.style.right = "";
133
+ tooltipEl.setAttribute("aria-hidden", "false");
116
134
  tooltipEl.classList.add("visible");
117
135
 
118
136
  // Position after layout
@@ -144,9 +162,17 @@ function showTooltipAt(target) {
144
162
 
145
163
  function hideTooltip() {
146
164
  if (tooltipEl) {
147
- tooltipEl.classList.remove("visible");
165
+ tooltipEl.classList.remove("visible"); tooltipEl.setAttribute("aria-hidden", "true");
166
+ }
167
+ if (currentTarget) {
168
+ var described = (currentTarget.getAttribute('aria-describedby') || '').split(/\s+/).filter(function (id) { return id && id !== 'clay-tooltip'; });
169
+ if (described.length) currentTarget.setAttribute('aria-describedby', described.join(' ')); else currentTarget.removeAttribute('aria-describedby');
148
170
  }
149
171
  currentTarget = null;
150
172
  }
151
173
 
152
- export { initTooltips, registerTooltip };
174
+ function pruneTooltip() { if (currentTarget && (!currentTarget.isConnected || !currentTarget.getClientRects().length)) { cancelShow(); hideTooltip(); } }
175
+
176
+ function dismissTooltip() { cancelShow(); hideTooltip(); }
177
+
178
+ export { initTooltips, registerTooltip, pruneTooltip, dismissTooltip };
@@ -0,0 +1,16 @@
1
+ import { copy, mapWorkspaceTree, paneOwner } from './view-workspace-state.js';
2
+ import { listIds } from './split-view-layout-client.js';
3
+ // Coordinates never create resources. Resolve the old singleton Files slot only
4
+ // after a real file instance exists; leave other delayed slots intact.
5
+ export function boundLegacyCoordinates(record, value) {
6
+ var next = copy(record), mapping = {};
7
+ listIds(record.tree).forEach(function (id) { mapping[id] = id; });
8
+ var files = value.panes.filter(function (pane) { return pane.kind === 'file' && pane.open && pane.placement === 'docked'; });
9
+ if (files.length === 1) mapping['tool-file-viewer'] = 'tool-' + paneOwner(files[0]);
10
+ next.tree = mapWorkspaceTree(next.tree, mapping);
11
+ return next;
12
+ }
13
+ export function legacyCoordinatesReady(record, value) {
14
+ var ids = value.panes.filter(function (p) { return p.open && p.placement === 'docked'; }).map(function (p) { return 'tool-' + paneOwner(p); });
15
+ return listIds(boundLegacyCoordinates(record, value).tree).filter(function (id) { return id.indexOf('tool-') === 0; }).every(function (id) { return ids.indexOf(id) >= 0; });
16
+ }
@@ -0,0 +1,24 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { showToast } from './utils.js';
4
+ import { prepareViewMutation } from './view-workspace-state.js';
5
+ function contextKey(state) { return [state.currentSlug || '', state.currentProjectOwnerId || '', state.myUserId || '', state.projectSplitViewSocketGeneration || 0].join('|'); }
6
+ function projectReady(state) { return getWs() !== state.splitIdentityBlockedSocket && !state.paneMode && !state.dmMode && state.connected && state.authIdentityLoaded && state.currentSlug === state.activeProjectSlug && state.currentSlug === state.sessionListProjectSlug; }
7
+ function sendMessage(message) { var ws = getWs(); if (!ws || ws.readyState !== 1 || !projectReady(store.snap())) return false; try { ws.send(JSON.stringify(message)); return true; } catch (error) { return false; } }
8
+ export function mutation(type, body, meta) {
9
+ var state = store.snap();
10
+ if (!projectReady(state) || !state.projectSplitViewHydrated || state.projectSplitViewError || state.projectSplitViewPending) return false;
11
+ var requestId = "split-view-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
12
+ var ws = getWs();
13
+ var message = prepareViewMutation(type, Object.assign({ type: type, requestId: requestId, expectedRevision: state.projectSplitViewRevision }, body || {}));
14
+ if (!message) return false;
15
+ var pending = { requestId: requestId, type: type, socket: ws, contextKey: contextKey(state), expectedRevision: state.projectSplitViewRevision, meta: meta || null,
16
+ awaitingBroadcast: false, resultRevision: null };
17
+ store.set({ projectSplitViewPending: pending, projectSplitViewError: "" });
18
+ if (!sendMessage(message)) {
19
+ store.set({ projectSplitViewPending: null, projectSplitViewError: "Split view change could not be sent. Refresh before trying again." });
20
+ showToast("Split view change could not be sent. Refresh before trying again.", "error");
21
+ return false;
22
+ }
23
+ return true;
24
+ }