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
@@ -5,13 +5,25 @@ import { checkComposerDeparture } from './session-rich-drafts.js';
5
5
  import { showToast } from './utils.js';
6
6
  import { listIds, place, swap, pairTree } from './split-view-layout-client.js';
7
7
 
8
+ // Preview and persistence must resolve the same tree for every drop region.
9
+ export function presentationDropTree(drag, target, sourceView, targetView) {
10
+ if (!target || target.folderId || drag.origin === target.origin) return null;
11
+ var sameView = sourceView && targetView && sourceView.id === targetView.id;
12
+ if (target.sidebar && sameView) return null;
13
+ var side = target.side === "center" ? (sameView ? "center" : "right") : target.side;
14
+ if (!targetView) return pairTree(target.origin, drag.origin, side);
15
+ var result = side === "center" ? swap(targetView.tree, drag.origin, target.origin) : place(targetView.tree, drag.origin, target.origin, side, 0.5);
16
+ return result.ok ? result.tree : null;
17
+ }
18
+
8
19
  export function applyPresentationDrop(drag, target, mutation, checkMountedPaneDrafts) {
9
20
  var views = store.get('projectSplitViews') || [], sourceView = views.find(function (view) { return view.id === drag.sourceViewId; }), targetView = views.find(function (view) { return view.id === target.viewId; });
10
21
  var sessions = getCachedSessions() || [], sourceSession = exactDriverForOrigin(drag.origin, sessions), targetSession = exactDriverForOrigin(target.origin, sessions);
11
22
  if (!sourceSession || !targetSession || sourceSession.id !== drag.sessionId) return;
12
23
  var sameView = sourceView && targetView && sourceView.id === targetView.id;
13
24
  if (target.sidebar && sameView) return;
14
- var side = target.side === "center" ? (sameView ? "center" : "right") : target.side;
25
+ var plannedTree = presentationDropTree(drag, target, sourceView, targetView);
26
+ if (!plannedTree) return false;
15
27
  function checkDeparture() {
16
28
  try {
17
29
  if (sourceView && sourceView.id === store.get('projectSplitViewActiveId')) checkMountedPaneDrafts();
@@ -22,36 +34,27 @@ export function applyPresentationDrop(drag, target, mutation, checkMountedPaneDr
22
34
  if (!targetView) {
23
35
  if (sourceView) {
24
36
  if (!checkDeparture()) return;
25
- var newTree = pairTree(target.origin, drag.origin, side);
26
- mutation("project_split_view_move", { sourceViewId: sourceView.id, sourceSessionOriginId: drag.origin,
37
+ var newTree = plannedTree;
38
+ return mutation("project_split_view_move", { sourceViewId: sourceView.id, sourceSessionOriginId: drag.origin,
27
39
  targetSessionOriginId: target.origin, targetSessionOrigins: listIds(newTree), tree: newTree, layout: "balanced",
28
40
  activeSessionOriginId: target.origin, name: "Split view" }, { activateCreatedView: true });
29
41
  } else {
30
42
  if (!checkDeparture()) return;
31
- var createdTree = pairTree(target.origin, drag.origin, side);
32
- mutation("project_split_view_create", { name: "Split view", sessionOrigins: listIds(createdTree), tree: createdTree,
43
+ var createdTree = plannedTree;
44
+ return mutation("project_split_view_create", { name: "Split view", sessionOrigins: listIds(createdTree), tree: createdTree,
33
45
  layout: "balanced", activeSessionOriginId: target.origin }, { activateCreatedView: true });
34
46
  }
35
- return;
36
47
  }
37
48
  if (sourceView && targetView.id !== sourceView.id) {
38
49
  if (targetView.sessions.length >= 8) { showToast("Eight sessions are already open. Close a pane to add another.", "warn"); return; }
39
- var movedTree = place(targetView.tree, drag.origin, target.origin, side, 0.5);
40
- if (!movedTree.ok) return;
41
- mutation("project_split_view_move", { sourceViewId: sourceView.id, sourceSessionOriginId: drag.origin,
50
+ var movedTree = { tree: plannedTree };
51
+ return mutation("project_split_view_move", { sourceViewId: sourceView.id, sourceSessionOriginId: drag.origin,
42
52
  targetViewId: targetView.id, targetSessionOrigins: listIds(movedTree.tree), tree: movedTree.tree,
43
53
  layout: targetView.layout, activeSessionOriginId: targetView.activeSessionOriginId }, target.sidebar ? { activateCreatedView: true, createdViewId: targetView.id } : null);
44
- return;
45
- }
46
- var nextTree;
47
- if (side === "center" && sourceView) nextTree = swap(targetView.tree, drag.origin, target.origin).tree;
48
- else {
49
- var placed = place(targetView.tree, drag.origin, target.origin, side, 0.5);
50
- if (!placed.ok) return;
51
- nextTree = placed.tree;
52
54
  }
55
+ var nextTree = plannedTree;
53
56
  var origins = listIds(nextTree);
54
57
  if (origins.length > 8) { showToast("Eight sessions are already open. Close a pane to add another.", "warn"); return; }
55
- mutation("project_split_view_update", { id: targetView.id, sessionOrigins: origins, tree: nextTree,
58
+ return mutation("project_split_view_update", { id: targetView.id, sessionOrigins: origins, tree: nextTree,
56
59
  layout: targetView.layout, activeSessionOriginId: targetView.activeSessionOriginId }, target.sidebar ? { activateCreatedView: true, createdViewId: targetView.id } : null);
57
60
  }
@@ -1,4 +1,7 @@
1
- import { layoutScope, currentLayoutScope, presentationTree } from './split-view-layout-scope.js';
1
+ import { paintLegacyToolPanes, clearToolPaneWindows } from './tool-pane-legacy.js';
2
+ import { toolPaneLayoutTree, paintToolPaneWindows, bindWorkspaceDivider, dockedToolPanes } from './tool-pane-layout.js';
3
+ import { afterPaneMotion } from './split-view-drag-preview.js';
4
+ import { layoutScope, currentLayoutScope, presentationTree, bindingMatches } from './split-view-layout-scope.js';
2
5
  // Flat, stable pane presentation for persisted binary trees. Geometry is
3
6
  // applied to existing direct-child .split-pane nodes; iframes are never moved
4
7
  // below a nested layout wrapper.
@@ -14,6 +17,7 @@ import { syncPaneHeaderState } from './split-pane-header-state.js';
14
17
  import { rectangles, resize, validateTree, listIds } from './split-view-layout-client.js';
15
18
  import { iconHtml, refreshIcons } from './icons.js';
16
19
  import { appendLayoutOption } from './split-view-layout-icons.js';
20
+ import { reserveEmptySplit, clearEmptySplit } from './split-session-empty-pane.js';
17
21
 
18
22
  var host = null;
19
23
  var app = null;
@@ -21,7 +25,6 @@ var menuTrigger = null;
21
25
  var resizeObserver = null;
22
26
  var geometryToken = 0;
23
27
  var stickyStatusOrigins = null;
24
-
25
28
  function relocateStickyStatus(enabled) {
26
29
  var ids = ['todo-sticky', 'ralph-sticky', 'debate-sticky'];
27
30
  var main = document.getElementById('main-panels'), appElement = document.getElementById('app');
@@ -41,7 +44,6 @@ function relocateStickyStatus(enabled) {
41
44
  });
42
45
  }
43
46
  }
44
-
45
47
  function dispatch(name, detail) {
46
48
  document.dispatchEvent(new CustomEvent(name, { detail: detail }));
47
49
  }
@@ -50,6 +52,7 @@ export function initPresentationRenderer(hostElement) {
50
52
  host = hostElement;
51
53
  app = document.getElementById("app");
52
54
  ensureArrangeMenu();
55
+ document.addEventListener('clay-split-picker-layout', renderCurrentGeometry);
53
56
  document.addEventListener("click", onClick, true);
54
57
  document.addEventListener("keydown", onKeyDown, true);
55
58
  document.addEventListener("pointermove", onPointerMove, true);
@@ -67,6 +70,7 @@ export function initPresentationRenderer(hostElement) {
67
70
 
68
71
  export function hidePresentationView() {
69
72
  if (!host) return;
73
+ clearEmptySplit(host);
70
74
  var legacySplit = store.get('splitPanes');
71
75
  if (legacySplit) releaseRetainedPanes(host);
72
76
  else clearRetainedPanes(host);
@@ -84,6 +88,7 @@ export function hidePresentationView() {
84
88
  delete host.dataset.positioned;
85
89
  delete host.dataset.singlePane;
86
90
  if (!legacySplit) {
91
+ clearToolPaneWindows();
87
92
  host.classList.remove("visible");
88
93
  if (app) app.classList.remove("split-native-hidden");
89
94
  }
@@ -98,7 +103,7 @@ export function hidePresentationView() {
98
103
  if (legacySplit) {
99
104
  Array.from(host.querySelectorAll(':scope > .split-view-divider, :scope > .split-view-drop-preview')).forEach(function (item) { item.remove(); });
100
105
  Array.from(host.querySelectorAll('.split-view-arrange-trigger')).forEach(function (trigger) { trigger.remove(); });
101
- closePresentationArrange(false);
106
+ closePresentationArrange(false); paintLegacyToolPanes(host);
102
107
  return;
103
108
  }
104
109
  var frames = host.querySelectorAll('.split-pane-frame');
@@ -169,8 +174,9 @@ function currentSingletonGeometry() {
169
174
  }
170
175
 
171
176
  function renderCurrentGeometry() {
177
+ if (paintLegacyToolPanes(host)) return;
172
178
  var view = currentView(), tree = activeTree();
173
- if (view && tree) { applyGeometry(view, tree); return; }
179
+ if (view && (tree || !view.sessions.length)) { applyGeometry(view, tree); return; }
174
180
  var singleton = currentSingletonGeometry();
175
181
  if (singleton) applyGeometry(singleton.view, { id: singleton.origin }, function () {
176
182
  var current = currentSingletonGeometry();
@@ -211,12 +217,15 @@ function applyGeometry(view, tree, stillCurrent) {
211
217
  });
212
218
  var style = window.getComputedStyle(host), left = parseFloat(style.paddingLeft) || 0, top = parseFloat(style.paddingTop) || 0;
213
219
  var right = parseFloat(style.paddingRight) || 0, bottom = parseFloat(style.paddingBottom) || 0;
214
- var geometry = rectangles(tree, Math.max(0, host.clientWidth - left - right), Math.max(0, host.clientHeight - top - bottom), 7);
220
+ var windowTree = toolPaneLayoutTree(view, tree);
221
+ var geometry = rectangles(windowTree, Math.max(0, host.clientWidth - left - right), Math.max(0, host.clientHeight - top - bottom), 7);
222
+ paintToolPaneWindows(host, view, tree, windowTree, geometry, { left: left, top: top });
223
+ var reserving = reserveEmptySplit(host, geometry, { left: left, top: top });
215
224
  var focused = store.get('projectSplitViewFocusedOrigin');
216
225
  var panes = host.querySelectorAll(':scope > .split-pane');
217
226
  for (var i = 0; i < panes.length; i++) {
218
227
  var frame = panes[i].querySelector('.split-pane-frame'), origin = panes[i].dataset.presentationOrigin || frame && frame.dataset.sessionOriginId;
219
- var rect = host.dataset.singlePane === 'true' && panes.length === 1 ?
228
+ var rect = !reserving && host.dataset.singlePane === 'true' && panes.length === 1 && !dockedToolPanes().length ?
220
229
  { x: 0, y: 0, width: Math.max(0, host.clientWidth - left - right), height: Math.max(0, host.clientHeight - top - bottom) } : geometry.panes[origin];
221
230
  if (!rect) continue;
222
231
  if (focused && geometry.panes[focused]) {
@@ -239,13 +248,15 @@ function applyGeometry(view, tree, stillCurrent) {
239
248
  divider.setAttribute("role", "separator"); divider.setAttribute("tabindex", "0");
240
249
  divider.setAttribute("aria-orientation", entry.axis === "horizontal" ? "vertical" : "horizontal");
241
250
  divider.setAttribute("aria-label", "Resize Split view panels");
251
+ if (reserving) { divider.inert = true; divider.tabIndex = -1; divider.setAttribute('aria-disabled', 'true'); }
242
252
  divider.style.left = (left + entry.rect.x) + "px"; divider.style.top = (top + entry.rect.y) + "px";
243
253
  divider.style.width = entry.rect.width + "px"; divider.style.height = entry.rect.height + "px";
244
- bindDivider(divider, entry, tree, listIds(tree), scope); host.appendChild(divider);
254
+ if (dockedToolPanes().length) bindWorkspaceDivider(divider, entry, windowTree);
255
+ else bindDivider(divider, entry, tree, listIds(tree), scope); host.appendChild(divider);
245
256
  if (focusedDividerPath && divider.dataset.path === focusedDividerPath) divider.focus();
246
257
  }
247
258
  ensureDropPreview();
248
- window.requestAnimationFrame(function () {
259
+ afterPaneMotion(host, function () {
249
260
  if (resizeToken !== 'split-view-layout-' + geometryToken || (stillCurrent ? !stillCurrent() : !currentLayoutScope(scope))) return;
250
261
  resizeFrames.forEach(function (item) {
251
262
  var live = item.frame.clayPane && item.frame.clayPane.identity();
@@ -254,17 +265,11 @@ function applyGeometry(view, tree, stillCurrent) {
254
265
  });
255
266
  });
256
267
  }
257
-
258
- function bindingMatches(left, right) {
259
- return !!left && !!right && left.sessionId === right.sessionId && left.sessionOriginId === right.sessionOriginId &&
260
- left.generation === right.generation && left.requestId === right.requestId;
261
- }
262
-
263
- function ensureArrangeTrigger(header) {
268
+ export function ensureArrangeTrigger(header) {
264
269
  if (!header) return;
265
270
  if (!header.querySelector('[data-action="arrange"]')) {
266
271
  var trigger = document.createElement("button"); trigger.type = "button"; trigger.className = "split-view-arrange-trigger";
267
- trigger.dataset.action = "arrange"; trigger.setAttribute("aria-label", "Arrange sessions");
272
+ trigger.dataset.action = "arrange"; trigger.setAttribute("aria-label", "Split view options");
268
273
  trigger.setAttribute("aria-expanded", "false"); trigger.setAttribute("aria-controls", "split-view-arrange-menu");
269
274
  trigger.innerHTML = iconHtml("columns-2");
270
275
  var close = header.querySelector(".split-pane-close"); header.insertBefore(trigger, close || null);
@@ -1,3 +1,9 @@
1
+ import { workspaceChanged } from './view-workspace-state.js';
2
+ import { restoreViewPair } from './view-pair-presentation.js';
3
+ import { repaintLegacySplit } from './split-view.js';
4
+ import { validWorkspaceView } from './view-workspace-validation.js';
5
+ import { mutation } from './view-mutations.js';
6
+ import { initSavedWorkspace } from './view-workspace-controller.js';
1
7
  import { preserveReconnectingPresentation } from './split-pane-retention.js';
2
8
  import { layoutScope, currentLayoutScope, presentationTree } from './split-view-layout-scope.js';
3
9
  import { validPresentationClose } from './split-view-presentation-close.js';
@@ -99,7 +105,7 @@ export function handleProjectSplitViews(msg) {
99
105
  var invalidView = false;
100
106
  msg.views.forEach(function (view) {
101
107
  var ids = view && Array.isArray(view.sessions) ? view.sessions.map(function (session) { return session.sessionOriginId; }) : [];
102
- if (ids.length < 2 || ids.length > 8 || viewSessions(view).length !== ids.length || !validateTree(view.tree, ids).ok) invalidView = true;
108
+ if (ids.length > 8 || viewSessions(view).length !== ids.length || !validateTree(view.tree, ids).ok || !validWorkspaceView(view)) invalidView = true;
103
109
  else nextViews.push(view);
104
110
  });
105
111
  if (invalidView) {
@@ -115,7 +121,7 @@ export function handleProjectSplitViews(msg) {
115
121
  if (state.projectSplitViewDrag || state.projectSplitViewDividerDrag) cleanupTransientInteractions(state);
116
122
  var activeId = state.projectSplitViewActiveId;
117
123
  if (!viewExists(nextViews, activeId)) {
118
- var restored = restorablePresentation(state, nextViews, getCachedSessions());
124
+ var restored = nextViews.find(function (view) { return view.id === msg.activeViewId && !view.sessions.length; }) || restorablePresentation(state, nextViews, getCachedSessions());
119
125
  if (restored) activeId = restored.id;
120
126
  }
121
127
  var removedActive = activeId && !viewExists(nextViews, activeId) ? activeId : null;
@@ -126,7 +132,7 @@ export function handleProjectSplitViews(msg) {
126
132
  var survivor = exactDriverForOrigin(completedPending.meta.activeOrigin, getCachedSessions());
127
133
  if (survivor) standaloneTarget = { id: survivor.id, origin: survivor.sessionOriginId, previousSessionId: state.activeSessionId, contextKey: contextKey(state) };
128
134
  }
129
- store.set({ projectSplitViews: nextViews, projectSplitViewRevision: msg.revision, projectSplitViewHydrated: true,
135
+ store.set({ projectSplitViews: nextViews, projectSplitViewRevision: msg.revision, projectSplitViewSavedActiveId: msg.activeViewId || null, projectSplitViewHydrated: true,
130
136
  projectSplitViewError: msg.error || "", projectSplitViewStandaloneTarget: standaloneTarget, projectSplitViewActiveId: viewExists(nextViews, activeId) ? activeId : null,
131
137
  projectSplitViewFocusedOrigin: viewExists(nextViews, activeId) ? state.projectSplitViewFocusedOrigin : null,
132
138
  projectSplitViewPending: readyPending, projectSplitViewPreviewTree: readyPending ? state.projectSplitViewPreviewTree : null,
@@ -182,22 +188,6 @@ export function organizePresentationView(id, revision, folderId, favorite) {
182
188
  if (!view || view.revision !== revision) return false;
183
189
  return mutation('project_split_view_organize', { id: id, expectedViewRevision: revision, projectSlug: store.get('currentSlug'), folderId: folderId, favorite: favorite });
184
190
  }
185
- function mutation(type, body, meta) {
186
- var state = store.snap();
187
- if (!projectReady(state) || !state.projectSplitViewHydrated || state.projectSplitViewError || state.projectSplitViewPending) return false;
188
- var requestId = "split-view-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
189
- var ws = getWs();
190
- var message = Object.assign({ type: type, requestId: requestId, expectedRevision: state.projectSplitViewRevision }, body || {});
191
- var pending = { requestId: requestId, type: type, socket: ws, contextKey: contextKey(state), expectedRevision: state.projectSplitViewRevision, meta: meta || null,
192
- awaitingBroadcast: false, resultRevision: null };
193
- store.set({ projectSplitViewPending: pending, projectSplitViewError: "" });
194
- if (!sendMessage(message)) {
195
- store.set({ projectSplitViewPending: null, projectSplitViewError: "Split view change could not be sent. Refresh before trying again." });
196
- showToast("Split view change could not be sent. Refresh before trying again.", "error");
197
- return false;
198
- }
199
- return true;
200
- }
201
191
  export function setActiveView(id, preferredOrigin) {
202
192
  var state = store.snap(), view = viewExists(state.projectSplitViews, id);
203
193
  if (!view) return false;
@@ -235,6 +225,7 @@ export function switchToStandalone(sessionId) {
235
225
  return true;
236
226
  }
237
227
  function renderActive() {
228
+ if (store.get('splitPanes')) { repaintLegacySplit(); return; }
238
229
  if (!host || !nativeApp || preserveReconnectingPresentation(host)) return;
239
230
  var state = store.snap(), view = viewExists(state.projectSplitViews, state.projectSplitViewActiveId);
240
231
  if (state.splitIdentityBlockedSocket === getWs()) { hidePresentationView(); return; }
@@ -246,9 +237,11 @@ function renderActive() {
246
237
  }
247
238
  var singleton = currentSingletonPresentation(state, getCachedSessions());
248
239
  if (singleton) {
249
- store.set({ projectSplitViewSingletonOrigin: singleton.origin });
250
- if (stickyContainer && mainPanels) mainPanels.appendChild(stickyContainer);
251
- renderPresentationView(singleton.view, { id: singleton.origin }, [singleton.session]);
240
+ if (state.projectSplitViewHydrated && !state.projectSplitViewPending && !state.projectSplitViewError) mutation("project_split_view_create", { name: "View", sessionOrigins: [singleton.origin], tree: { id: singleton.origin }, layout: "balanced", activeSessionOriginId: singleton.origin }, { activateCreatedView: true });
241
+ return;
242
+ }
243
+ if (state.projectSplitViewHydrated && projectReady(state) && !(getCachedSessions() || []).length && !state.projectSplitViewPending && !state.projectSplitViewError) {
244
+ mutation('project_split_view_create', { name: 'View', sessionOrigins: [], tree: null, layout: 'balanced', activeSessionOriginId: null }, { activateCreatedView: true });
252
245
  return;
253
246
  }
254
247
  if (state.projectSplitViewSingletonOrigin) store.set({ projectSplitViewSingletonOrigin: null });
@@ -257,10 +250,11 @@ function renderActive() {
257
250
  return;
258
251
  }
259
252
  if (state.projectSplitViewSingletonOrigin) store.set({ projectSplitViewSingletonOrigin: null });
253
+ if (restoreViewPair(view)) return;
260
254
  var sessions = viewSessions(view);
261
255
  var tree = presentationTree(view);
262
256
  var ids = listIds(tree);
263
- if (sessions.length !== view.sessions.length || ids.length < 2 || ids.length > 8 || !validateTree(tree, ids).ok) return;
257
+ if (sessions.length !== view.sessions.length || ids.length > 8 || !validateTree(tree, ids).ok) return;
264
258
  if (stickyContainer && mainPanels) mainPanels.appendChild(stickyContainer);
265
259
  renderPresentationView(view, tree, sessions);
266
260
  }
@@ -424,14 +418,9 @@ function onClosePane(event) {
424
418
  if (!session || !view.sessions.some(function (item) { return item.sessionOriginId === session.sessionOriginId; })) return;
425
419
  try { checkMountedPaneDrafts(); } catch (error) { showToast(error.message, "warn"); return; }
426
420
  var origins = view.sessions.map(function (item) { return item.sessionOriginId; }).filter(function (origin) { return origin !== session.sessionOriginId; });
427
- if (origins.length < 2) {
428
- var remaining = view.sessions.filter(function (item) { return item.sessionOriginId !== session.sessionOriginId; })[0];
429
- if (remaining) mutation("project_split_view_separate", { id: view.id }, { separateViewId: view.id, activeOrigin: remaining.sessionOriginId });
430
- return;
431
- }
432
421
  var nextTree = remove(view.tree, session.sessionOriginId);
433
- if (!nextTree || !validateTree(nextTree, origins).ok) return;
434
- var nextActive = view.activeSessionOriginId === session.sessionOriginId ? origins[0] : view.activeSessionOriginId;
422
+ if (!validateTree(nextTree, origins).ok) return;
423
+ var nextActive = view.activeSessionOriginId === session.sessionOriginId ? origins[0] || null : view.activeSessionOriginId;
435
424
  mutation("project_split_view_update", { id: view.id, sessionOrigins: origins, tree: nextTree,
436
425
  layout: view.layout, activeSessionOriginId: nextActive }, { closedOrigin: session.sessionOriginId, activeOrigin: nextActive });
437
426
  }
@@ -442,13 +431,15 @@ function onFocus(event) {
442
431
  if (event.detail && event.detail.focused && !source) return;
443
432
  store.set({ projectSplitViewFocusedOrigin: event.detail && event.detail.focused ? source.sessionOriginId : null });
444
433
  renderActive();
434
+ if (!store.get('savedWorkspaceApplying')) workspaceChanged();
445
435
  }
446
436
  function init(hostElement) {
447
437
  host = hostElement; nativeApp = document.getElementById("app"); mainPanels = document.getElementById("main-panels");
448
438
  stickyContainer = document.getElementById("sticky-notes-container");
449
439
  if (stickyContainer) { stickyHome = stickyContainer.parentNode; stickyAnchor = stickyContainer.nextSibling; }
450
440
  initPresentationRenderer(host);
451
- initPresentationDrag(host, function (drag, target) { applyPresentationDrop(drag, target, mutation, checkMountedPaneDrafts); });
441
+ initSavedWorkspace();
442
+ initPresentationDrag(host, function (drag, target) { return applyPresentationDrop(drag, target, mutation, checkMountedPaneDrafts); });
452
443
  initSplitViewCreateMenu();
453
444
  document.addEventListener('clay-split-view-create', function (event) { handleSplitViewCreateEvent(event, mutation, checkMountedPaneDrafts); });
454
445
  document.addEventListener("click", onClick, true);
@@ -476,7 +467,7 @@ function init(hostElement) {
476
467
  state.paneBindingSessions !== previous.paneBindingSessions || state.sessionActivatedProjectSlug !== previous.sessionActivatedProjectSlug) renderActive();
477
468
  if (state.activeSessionId !== previous.activeSessionId && state.projectSplitViewActiveId) {
478
469
  var activeView = viewExists(state.projectSplitViews, state.projectSplitViewActiveId);
479
- if (activeView && !activeView.sessions.some(function (session) { return session.id === state.activeSessionId; })) {
470
+ if (activeView && activeView.sessions.length && !activeView.sessions.some(function (session) { return session.id === state.activeSessionId; })) {
480
471
  store.set({ projectSplitViewActiveId: null, projectSplitViewPreviewTree: null });
481
472
  }
482
473
  }
@@ -493,6 +484,7 @@ export function applyReviewedSplitView(target) {
493
484
  var view = viewExists(store.get('projectSplitViews'), target.view.id);
494
485
  if (!projectReady(store.snap()) || store.get('projectSplitViewPending') || !view || JSON.stringify(view) !== JSON.stringify(target.view)) return false;
495
486
  if (!setActiveView(view.id, view.activeSessionOriginId)) return false;
496
- onFocus({ detail: { focused: !!target.focusOrigin, sessionOriginId: target.focusOrigin } });
487
+ store.set({ savedWorkspaceApplying: true });
488
+ try { onFocus({ detail: { focused: !!target.focusOrigin, sessionOriginId: target.focusOrigin } }); } finally { store.set({ savedWorkspaceApplying: false }); }
497
489
  return true;
498
490
  }
@@ -22,14 +22,14 @@ export function workerActivityLabel(message, names) {
22
22
  }
23
23
  export function captureWorkerDisclosures(body) {
24
24
  return {
25
- open: Array.from(body.querySelectorAll('details[open]')).map(function (node) { return node.dataset.disclosureKey; }),
25
+ open: Array.from(body.querySelectorAll('details[open], [data-disclosure-key][aria-expanded="true"]')).map(function (node) { return node.dataset.disclosureKey; }),
26
26
  focus: document.activeElement && body.contains(document.activeElement) && (document.activeElement.dataset.disclosureKey || document.activeElement.parentElement && document.activeElement.parentElement.dataset.disclosureKey)
27
27
  };
28
28
  }
29
29
  export function restoreWorkerDisclosures(body, saved) {
30
30
  Array.from(body.querySelectorAll('[data-disclosure-key]')).forEach(function (node) {
31
31
  var key = node.dataset.disclosureKey;
32
- if (saved.open.indexOf(key) >= 0) node.open = true;
32
+ if (saved.open.indexOf(key) >= 0) { if (node.tagName === 'DETAILS') node.open = true; else if (node.getAttribute('aria-expanded') !== 'true') node.click(); }
33
33
  if (saved.focus === key) {
34
34
  var target = node.tagName === 'DETAILS' ? node.querySelector('summary') : node;
35
35
  if (target) target.focus({ preventScroll: true });
@@ -1,5 +1,9 @@
1
- import { workerActivityLabel, workerActivityNames } from './split-view-worker-activity.js';
2
- import { renderMarkdown } from './markdown.js';
1
+ import { workerActivityNames } from './split-view-worker-activity.js';
2
+ import { createAssistantBubble, createUserBubble, renderAssistantBubbleText } from './chat-bubble-renderer.js';
3
+ import { createSessionToolItem, createSessionToolGroup, sessionToolGroupSummary } from './session-tool-item.js';
4
+ import { applyClayToolItem, bindToolGroupDisclosure } from './clay-tool-activity.js';
5
+ import { iconHtml } from './icons.js';
6
+ import { VENDOR_AVATARS } from './chat-render-runtime.js';
3
7
 
4
8
  // Reassemble bounded record fragments and contiguous streaming deltas before
5
9
  // parsing Markdown, including fences and lists that cross a page boundary.
@@ -17,38 +21,71 @@ export function workerTranscriptRows(messages) {
17
21
  return rows;
18
22
  }
19
23
 
20
- export function appendWorkerMessage(body, message, names) {
24
+ function inputFor(message) {
25
+ if (message.recordType !== 'tool_use' && message.recordType !== 'tool_executing') return null;
26
+ try { var start = message.text.indexOf('{'); return start >= 0 ? JSON.parse(message.text.slice(start)) : null; } catch (error) { return null; }
27
+ }
28
+ export function appendWorkerMessage(body, message, names, options) {
29
+ options = options || {};
21
30
  if (message.role === 'tool') {
22
- var article = document.createElement('article');
23
- article.className = 'message worker-readonly-message worker-tool-message';
24
- article.dataset.recordIndex = String(message.recordIndex);
25
- article.dataset.phase = message.isError ? 'failed' : message.recordType === 'tool_result' ? 'returned' : 'recorded';
26
- var details = document.createElement('details');
27
- details.dataset.disclosureKey = 'record-' + message.recordIndex;
28
- var summary = document.createElement('summary');
29
- summary.textContent = workerActivityLabel(message, names || workerActivityNames([message])) +
30
- (message.isError ? ' · Failed' : message.recordType === 'tool_result' ? ' · Result returned' : ' · Call recorded');
31
- var output = document.createElement('pre'); output.textContent = message.text;
32
- details.append(summary, output); article.appendChild(details); body.appendChild(article);
33
- return;
31
+ var name = message.toolName || names && names[message.toolId] && names[message.toolId].name || 'Tool activity';
32
+ var el = createSessionToolItem(message.toolId, name);
33
+ el.classList.add('worker-readonly-message', 'worker-tool-message'); el.dataset.recordIndex = String(message.recordIndex);
34
+ el.dataset.phase = message.isError ? 'failed' : message.recordType === 'tool_result' ? 'returned' : 'recorded';
35
+ var input = inputFor(message), output = message.resultText;
36
+ var tool = { el: el, name: name, input: input, clayRawResult: output, clayOutcome: output !== undefined ? { phase: message.isError ? 'error' : 'done' } : { phase: 'returned', status: 'result_unavailable' } };
37
+ var header = el.querySelector('.tool-header'); header.dataset.disclosureKey = 'record-' + message.recordIndex;
38
+ if (!applyClayToolItem(tool)) {
39
+ var terminal = message.isError ? 'Failed' : output !== undefined || message.recordType === 'tool_result' ? 'Result returned' : 'Call recorded';
40
+ el.querySelector('.tool-desc').textContent = input && (input.file_path || input.command || input.pattern) || '';
41
+ el.querySelector('.tool-subtitle-text').textContent = terminal;
42
+ el.querySelector('.tool-status-icon').innerHTML = iconHtml(message.isError ? 'alert-triangle' : output !== undefined ? 'check' : 'circle-help');
43
+ el.classList.toggle('done', output !== undefined); el.classList.toggle('error', !!message.isError);
44
+ var block = document.createElement('div'); block.className = 'tool-result-block collapsed';
45
+ var pre = document.createElement('pre'); pre.textContent = output !== undefined ? output : message.text; block.appendChild(pre); el.appendChild(block);
46
+ header.setAttribute('role', 'button'); header.tabIndex = 0; header.setAttribute('aria-expanded', 'false');
47
+ function toggle() { var expanded = el.classList.toggle('expanded'); block.classList.toggle('collapsed', !expanded); header.setAttribute('aria-expanded', String(expanded)); }
48
+ header.addEventListener('click', toggle); header.addEventListener('keydown', function (event) { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); toggle(); } });
49
+ }
50
+ body.appendChild(el); return;
34
51
  }
35
- var article = document.createElement('article');
36
- article.className = 'message worker-readonly-message ' + (message.role === 'user' ? 'msg-user' : 'msg-assistant');
37
- if (Number.isInteger(message.recordIndex)) article.dataset.recordIndex = String(message.recordIndex);
38
- var author = document.createElement('strong'); author.className = 'author';
39
- author.textContent = message.role === 'user' ? 'Driver' : 'Worker';
40
- var content = document.createElement('div'); content.className = 'message-body md-content';
41
- // Recorded HTML must not create controls, scoped overlays or active embeds.
42
- // Keep the shared Markdown parser and its sanitization, with a narrower
43
- // read-only prose vocabulary for this secondary history surface.
44
- content.innerHTML = DOMPurify.sanitize(renderMarkdown(message.text), {
45
- ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'del', 's', 'blockquote', 'ul', 'ol', 'li', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'a', 'wbr'],
46
- ALLOWED_ATTR: ['href', 'title', 'target', 'rel', 'start'], ALLOW_DATA_ATTR: false
47
- });
48
- article.append(author, content); body.appendChild(article);
52
+ var user = message.role === 'user';
53
+ var label = message.authorLabel || (user ? options.userName || 'Driver' : options.assistantName || 'Worker');
54
+ var config = { tagName: 'article', name: label, avatarUrl: user ? options.userAvatar || '/icon-192.png' : VENDOR_AVATARS[options.vendor || 'claude'], text: message.text };
55
+ var article = user ? createUserBubble(config) : createAssistantBubble(config);
56
+ article.classList.add('worker-readonly-message'); article.dataset.recordIndex = String(message.recordIndex);
57
+ if (!user) renderAssistantBubbleText(article, message.text, true, { readOnly: true });
58
+ body.appendChild(article);
49
59
  }
50
-
51
- export function appendWorkerTranscript(body, messages) {
52
- var rows = workerTranscriptRows(messages), names = workerActivityNames(rows);
53
- rows.forEach(function (message) { appendWorkerMessage(body, message, names); });
60
+ export function appendWorkerTranscript(body, messages, options) {
61
+ var rows = workerTranscriptRows(messages), names = workerActivityNames(rows), consumed = new Set(), visible = [];
62
+ rows.forEach(function (message, index) {
63
+ if (consumed.has(index)) return;
64
+ var row = message;
65
+ if (message.role === 'tool' && names[message.toolId] && names[message.toolId].index === message.recordIndex) {
66
+ var later = rows.slice(index + 1), executing = later.find(function (item) { return item.toolId === message.toolId && item.recordType === 'tool_executing'; });
67
+ var result = later.find(function (item) { return item.toolId === message.toolId && item.recordType === 'tool_result'; });
68
+ row = Object.assign({}, message, executing ? { text: executing.text, recordType: executing.recordType } : {}, result ? { resultText: result.text, isError: result.isError } : {});
69
+ [executing, result].forEach(function (item) { if (item) consumed.add(rows.indexOf(item)); });
70
+ }
71
+ visible.push(row);
72
+ });
73
+ var group = null, groupRows = [];
74
+ function finish() {
75
+ if (!group) return;
76
+ var header = group.querySelector('.tool-group-header'), done = groupRows.every(function (row) { return row.resultText !== undefined || row.recordType === 'tool_result'; });
77
+ var meta = { toolNames: groupRows.map(function (row) { return row.toolName || names[row.toolId] && names[row.toolId].name || 'Tool'; }), doneCount: done ? groupRows.length : 0 };
78
+ header.style.display = groupRows.length > 1 ? '' : 'none';
79
+ header.dataset.disclosureKey = 'group-' + groupRows[0].recordIndex;
80
+ group.querySelector('.tool-group-label').textContent = done ? sessionToolGroupSummary(meta, {}) : groupRows.length + ' tool calls recorded';
81
+ group.classList.toggle('done', done); group.classList.toggle('collapsed', groupRows.length > 1);
82
+ group.querySelector('.tool-group-status-icon').innerHTML = iconHtml(done ? 'check' : 'circle-help');
83
+ bindToolGroupDisclosure(group, function () {}); group = null; groupRows = [];
84
+ }
85
+ visible.forEach(function (row) {
86
+ if (row.role !== 'tool') { finish(); appendWorkerMessage(body, row, names, options); return; }
87
+ if (!group) { group = createSessionToolGroup(); body.appendChild(group); }
88
+ groupRows.push(row); appendWorkerMessage(group.querySelector('.tool-group-items'), row, names, options);
89
+ });
90
+ finish();
54
91
  }
@@ -1,3 +1,4 @@
1
+ import { syncWorkerMessagePreviews } from './worker-message-previews.js';
1
2
  import { appendWorkerRemoval, reconcileWorkerRemoval } from './split-view-worker-removal.js';
2
3
  import { workerCursorKey, workerCursorAdvances, shouldLoadWorkerPage, captureWorkerAnchor, restoreWorkerAnchor } from './split-view-worker-scroll.js';
3
4
  import { appendWorkerTranscript } from './split-view-worker-transcript.js';
@@ -189,7 +190,7 @@ function open(chip, driver, worker, takeFocus) {
189
190
  if (existing && existing.driverOriginId === driver.sessionOriginId && existing.workerOriginId === worker.sessionOriginId && existing.generation === worker.workerGeneration) { close(true); return; }
190
191
  if (existing) close(false);
191
192
  var state = { requestId: null, driverOriginId: driver.sessionOriginId, workerOriginId: worker.sessionOriginId,
192
- generation: worker.workerGeneration, driverTitle: driver.title || "Driver session", workerTitle: worker.title || "Worker",
193
+ generation: worker.workerGeneration, workerVendor: worker.vendor, driverTitle: driver.title || "Driver session", workerTitle: worker.title || "Worker",
193
194
  sourceBinding: chip.closest('.split-pane').querySelector('.split-pane-frame').clayPane.identity().binding,
194
195
  status: "loading", messages: [], hasMore: false, nextBefore: null, append: false, inFlight: false, initialPage: true };
195
196
  store.set({ splitViewWorkerConversation: state }); ensurePopover(); renderContent();
@@ -217,7 +218,7 @@ function ensurePopover() {
217
218
  var closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.setAttribute("aria-label", "Close Worker conversation"); closeButton.innerHTML = iconHtml("x");
218
219
  closeButton.addEventListener("click", function () { close(true); }); header.appendChild(closeButton); element.appendChild(header);
219
220
  var source = document.createElement("div"); source.className = "split-view-worker-popover-source"; element.appendChild(source);
220
- var body = document.createElement("div"); body.className = "split-view-worker-popover-body"; body.tabIndex = 0; body.setAttribute("aria-label", "Read-only Worker transcript"); element.appendChild(body);
221
+ var body = document.createElement("div"); body.className = "split-view-worker-popover-body readonly-session-transcript"; body.tabIndex = 0; body.setAttribute("aria-label", "Read-only Worker transcript"); element.appendChild(body);
221
222
  body.addEventListener('scroll', function () { loadEarlier(body, true); }, { passive: true });
222
223
  body.addEventListener('wheel', function (event) { if (event.deltaY < 0) loadEarlier(body, true); }, { passive: true });
223
224
  var footer = document.createElement("footer"); footer.className = "split-view-worker-popover-footer"; element.appendChild(footer);
@@ -241,7 +242,7 @@ function renderContent() {
241
242
  if (state.status === "loading" && !state.messages.length) body.textContent = "Loading conversation…";
242
243
  else if (state.status === "error" && !state.messages.length) body.textContent = state.error || "Conversation is unavailable.";
243
244
  else if (!state.messages.length) body.textContent = "No recorded conversation.";
244
- appendWorkerTranscript(body, state.messages);
245
+ appendWorkerTranscript(body, state.messages, { vendor: state.workerVendor, assistantName: state.workerTitle });
245
246
  restoreWorkerDisclosures(body, disclosures);
246
247
  if ((state.initialPage || state.liveRefresh && followTail) && state.status === 'ready') body.scrollTop = body.scrollHeight;
247
248
  else if (anchor) restoreWorkerAnchor(body, anchor);
@@ -370,7 +371,7 @@ export function renderPaneWorkers(container, driver) {
370
371
  data.items.forEach(function (worker) {
371
372
  var pill = document.createElement("span"); pill.className = "split-view-worker-pill";
372
373
  var button = document.createElement("button"); button.type = "button"; button.className = "split-view-worker-chip";
373
- button.dataset.workerOrigin = worker.sessionOriginId; button.dataset.workerGeneration = String(worker.workerGeneration);
374
+ button.dataset.workerParent = driver.sessionOriginId; button.dataset.workerOrigin = worker.sessionOriginId; button.dataset.workerGeneration = String(worker.workerGeneration);
374
375
  var current = data.currentIds.has(worker.id);
375
376
  var modelLabel = worker.model || "Default model";
376
377
  button.title = (VENDOR_NAMES[worker.vendor] || worker.vendor || "Unknown vendor") + " · " + modelLabel + " · Split Worker #" + worker.id +
@@ -419,6 +420,7 @@ export function renderPaneWorkers(container, driver) {
419
420
  root.appendChild(controls);
420
421
  }
421
422
  introduceWorkerViewer(container, driver);
423
+ syncWorkerMessagePreviews();
422
424
  var conversation = store.get('splitViewWorkerConversation');
423
425
  if (conversation) ensurePopover();
424
426
  }
@@ -1,3 +1,5 @@
1
+ import { syncPairView, pairPresentationPanes, pairViewCapacity } from './view-pair-presentation.js';
2
+ import { paintLegacyToolPanes } from './tool-pane-legacy.js';
1
3
  import { adoptDocumentSource } from './clay-studio-document.js';
2
4
  import { trustedStudioFrame } from './clay-studio-render.js';
3
5
  import { handlePaneBrowserMessage } from './pane-browser-bridge.js';
@@ -185,7 +187,8 @@ function renderSplit(split) {
185
187
  singleton || split && split.panes && split.panes.length >= 2 || !!state.projectSplitViewActiveId);
186
188
  // The exact singleton presentation is verified before the redundant cache catches up.
187
189
  if (singleton) { renderPresentationSplitView(); return; }
188
- if (split && split.panes && store.get('projectSplitViewActiveId')) {
190
+ var savedPair = split && split.panes && syncPairView();
191
+ if (split && split.panes && !savedPair && store.get('projectSplitViewActiveId')) {
189
192
  store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null });
190
193
  }
191
194
  var expectedPresentationContext = [store.get('currentSlug') || "", store.get('currentProjectOwnerId') || "", store.get('myUserId') || "", store.get('projectSplitViewSocketGeneration') || 0].join("|");
@@ -213,8 +216,10 @@ function renderSplit(split) {
213
216
  placeStickyNotesOverlay(true);
214
217
  nativeApp.classList.add("split-native-hidden");
215
218
  host.classList.add("visible");
219
+ if (savedPair) panes = pairPresentationPanes(panes);
216
220
  reconcileSplitPanes(host, panes, function (pane) { return createSplitPane(pane, closePane); }, updateSplitPaneMetadata);
217
221
  syncPairChrome(host, split);
222
+ paintLegacyToolPanes(host);
218
223
  refreshIcons();
219
224
  }
220
225
 
@@ -246,7 +251,7 @@ function openSplit(side, draggedId) {
246
251
 
247
252
  export function openGroup(group) {
248
253
  if (!canLeavePanes()) return false;
249
- if (!group || !Array.isArray(group.members) || group.members.length < 2) return false;
254
+ if (!group || !Array.isArray(group.members) || group.members.length < 2 || !pairViewCapacity(group)) return false;
250
255
  var memberIds = splitGroupMemberIds(group);
251
256
  if (memberIds.length !== group.members.length) return false;
252
257
  for (var mi = 0; mi < memberIds.length; mi++) if (!sessionById(memberIds[mi])) return false;
@@ -491,3 +496,5 @@ export function initSplitView() {
491
496
  initSplitViewPresentations(host);
492
497
  renderSplit(store.get('splitPanes'));
493
498
  }
499
+
500
+ export function repaintLegacySplit() { if (store.get('splitPanes')) renderSplit(store.get('splitPanes')); }
@@ -14,7 +14,7 @@ import { enhanceClayLogLinks } from './clay-log-links.js';
14
14
  import { closeHumanNote } from './sticky-notes-actions.js';
15
15
  import { send, getNoteUiState } from './sticky-notes-shared.js';
16
16
  import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
17
- import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
17
+ import { handlesWorkbenchKey, claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
18
18
 
19
19
  var panel = null;
20
20
  var gridEl = null;
@@ -427,7 +427,7 @@ export function isNotesBrowserOpen() {
427
427
  export function initNotesBrowser() {
428
428
  registerRightWorkbench("notes-browser", closeNotesBrowser);
429
429
  document.addEventListener("keydown", function (e) {
430
- if (e.key !== "Escape") return;
430
+ if (e.key !== "Escape" || !handlesWorkbenchKey("notes-browser", e)) return;
431
431
  if (deleteDialog && !deleteDialog.classList.contains("hidden")) return;
432
432
  if (!store.get('notesBrowserOpen')) return;
433
433
  closeNotesBrowser();
@@ -0,0 +1,14 @@
1
+ // Attachment snapshots describe the previous PTY size, not the current pane.
2
+ export function hasTerminalViewport(tab) {
3
+ var rect = tab && tab.bodyEl && tab.bodyEl.getBoundingClientRect();
4
+ return !!(rect && rect.width > 0 && rect.height > 0);
5
+ }
6
+
7
+ export function applyTerminalSize(tab, message, active, refit) {
8
+ if (!tab || !tab.xterm || !(message.cols > 0 && message.rows > 0)) return;
9
+ if (message.initial === true && active) {
10
+ if (hasTerminalViewport(tab)) refit();
11
+ return;
12
+ }
13
+ tab.xterm.resize(message.cols, message.rows);
14
+ }