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

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 (154) hide show
  1. package/lib/clay-control-presentation-binding.js +5 -3
  2. package/lib/clay-product-control-tools.js +3 -3
  3. package/lib/clay-product-control.js +4 -2
  4. package/lib/clay-screen-controls.js +18 -3
  5. package/lib/clay-split-view-awareness.js +35 -0
  6. package/lib/clay-split-view-controls.js +122 -0
  7. package/lib/clay-split-view-input.js +20 -0
  8. package/lib/clay-split-view-render-proof.js +22 -0
  9. package/lib/pane-asset-manifest.js +63 -0
  10. package/lib/project-connection.js +19 -10
  11. package/lib/project-session-actions.js +1 -0
  12. package/lib/project-session-delete.js +2 -2
  13. package/lib/project-session-folders.js +1 -0
  14. package/lib/project-sessions.js +6 -1
  15. package/lib/project-split-view-document.js +33 -0
  16. package/lib/project-split-view-names.js +67 -0
  17. package/lib/project-split-view-organization.js +23 -0
  18. package/lib/project-split-views.js +479 -0
  19. package/lib/project-worker-conversation.js +222 -0
  20. package/lib/project-worker-creation-proposal.js +3 -4
  21. package/lib/project-worker-proposal.js +1 -1
  22. package/lib/project-worker-removal.js +101 -0
  23. package/lib/project.js +28 -1
  24. package/lib/public/app.js +32 -13
  25. package/lib/public/css/base.css +9 -0
  26. package/lib/public/css/filebrowser.css +0 -1
  27. package/lib/public/css/pane-header-controls.css +100 -0
  28. package/lib/public/css/pane.css +24 -41
  29. package/lib/public/css/popup-chat.css +123 -0
  30. package/lib/public/css/right-workbench.css +50 -18
  31. package/lib/public/css/session-folders.css +1 -1
  32. package/lib/public/css/split-view-layout.css +463 -0
  33. package/lib/public/css/split-view-worker-activity.css +97 -0
  34. package/lib/public/css/sticky-notes.css +13 -0
  35. package/lib/public/index.html +3 -3
  36. package/lib/public/modules/app-connection.js +4 -1
  37. package/lib/public/modules/app-favicon.js +1 -1
  38. package/lib/public/modules/app-messages.js +31 -9
  39. package/lib/public/modules/chat-render-runtime.js +54 -0
  40. package/lib/public/modules/clay-control-copy.js +3 -0
  41. package/lib/public/modules/clay-navigation-render.js +1 -1
  42. package/lib/public/modules/clay-owner-navigation.js +15 -5
  43. package/lib/public/modules/clay-split-view-screen.js +74 -0
  44. package/lib/public/modules/clay-tool-activity.js +5 -0
  45. package/lib/public/modules/command-palette.js +30 -28
  46. package/lib/public/modules/contextual-clay-session.js +26 -2
  47. package/lib/public/modules/contextual-clay.js +5 -2
  48. package/lib/public/modules/contextual-mate-sessions.js +2 -0
  49. package/lib/public/modules/filebrowser.js +6 -2
  50. package/lib/public/modules/generation-stop.js +7 -1
  51. package/lib/public/modules/knowledge-workbench-entry.js +43 -0
  52. package/lib/public/modules/markdown-view-mode.js +34 -0
  53. package/lib/public/modules/markdown.js +11 -0
  54. package/lib/public/modules/mate-knowledge-workbench.js +2 -7
  55. package/lib/public/modules/mate-sidebar.js +1 -1
  56. package/lib/public/modules/palette-clay-conversation.js +87 -0
  57. package/lib/public/modules/palette-render-helpers.js +25 -0
  58. package/lib/public/modules/pane-activation.js +46 -7
  59. package/lib/public/modules/pane-binding.js +12 -2
  60. package/lib/public/modules/pane-bridge.js +12 -3
  61. package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
  62. package/lib/public/modules/pane-feature-assets.js +35 -0
  63. package/lib/public/modules/pane-file-bridge.js +8 -3
  64. package/lib/public/modules/pane-search-receipt.js +12 -0
  65. package/lib/public/modules/permission-control.js +6 -6
  66. package/lib/public/modules/popup-chat-rendering.js +244 -0
  67. package/lib/public/modules/project-activation.js +2 -2
  68. package/lib/public/modules/qrcode.js +12 -1
  69. package/lib/public/modules/right-workbench.js +68 -0
  70. package/lib/public/modules/search-hints.js +79 -0
  71. package/lib/public/modules/session-context-menu.js +35 -2
  72. package/lib/public/modules/session-folder-collapse.js +2 -1
  73. package/lib/public/modules/session-folder-context.js +24 -13
  74. package/lib/public/modules/session-folder-delete.js +18 -1
  75. package/lib/public/modules/session-folder-dnd.js +4 -1
  76. package/lib/public/modules/session-folder-layout.js +7 -6
  77. package/lib/public/modules/session-search-pane-state.js +30 -0
  78. package/lib/public/modules/session-search.js +53 -13
  79. package/lib/public/modules/sidebar-mobile.js +3 -3
  80. package/lib/public/modules/sidebar-projects.js +1 -0
  81. package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
  82. package/lib/public/modules/sidebar-sessions.js +57 -24
  83. package/lib/public/modules/sidebar.js +20 -3
  84. package/lib/public/modules/split-group-helpers.js +1 -1
  85. package/lib/public/modules/split-group-restore.js +8 -11
  86. package/lib/public/modules/split-pair-ui.js +18 -0
  87. package/lib/public/modules/split-pane-header-state.js +45 -0
  88. package/lib/public/modules/split-pane-reconciler.js +23 -4
  89. package/lib/public/modules/split-pane-renderer.js +330 -23
  90. package/lib/public/modules/split-pane-retention.js +157 -0
  91. package/lib/public/modules/split-session-boundary.js +21 -0
  92. package/lib/public/modules/split-view-create-action.js +15 -0
  93. package/lib/public/modules/split-view-create-menu.js +83 -0
  94. package/lib/public/modules/split-view-create-request.js +23 -0
  95. package/lib/public/modules/split-view-layout-client.js +198 -0
  96. package/lib/public/modules/split-view-layout-icons.js +37 -0
  97. package/lib/public/modules/split-view-layout-scope.js +38 -0
  98. package/lib/public/modules/split-view-organization.js +73 -0
  99. package/lib/public/modules/split-view-presentation-close.js +35 -0
  100. package/lib/public/modules/split-view-presentation-drag.js +278 -0
  101. package/lib/public/modules/split-view-presentation-drop.js +57 -0
  102. package/lib/public/modules/split-view-presentation-lookup.js +44 -0
  103. package/lib/public/modules/split-view-presentation-renderer.js +494 -0
  104. package/lib/public/modules/split-view-presentations.js +498 -0
  105. package/lib/public/modules/split-view-shell-controls.js +28 -0
  106. package/lib/public/modules/split-view-sidebar-menu.js +42 -0
  107. package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
  108. package/lib/public/modules/split-view-singleton.js +21 -0
  109. package/lib/public/modules/split-view-worker-activity.js +94 -0
  110. package/lib/public/modules/split-view-worker-controls.js +30 -0
  111. package/lib/public/modules/split-view-worker-removal.js +137 -0
  112. package/lib/public/modules/split-view-worker-scroll.js +57 -0
  113. package/lib/public/modules/split-view-worker-transcript.js +46 -0
  114. package/lib/public/modules/split-view-workers.js +418 -0
  115. package/lib/public/modules/split-view.js +61 -22
  116. package/lib/public/modules/sticky-notes.js +9 -2
  117. package/lib/public/modules/tools.js +25 -5
  118. package/lib/public/modules/workbench-layout-preference.js +76 -0
  119. package/lib/public/modules/worker-created-presentation.js +37 -0
  120. package/lib/public/pane-assets/app-2DRBYY3L.js +1 -0
  121. package/lib/public/pane-assets/chunk-4BM7UVNG.js +2 -0
  122. package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
  123. package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
  124. package/lib/public/pane-assets/chunk-C4RVVYNT.js +1 -0
  125. package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
  126. package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
  127. package/lib/public/pane-assets/chunk-O6MQL6MR.js +167 -0
  128. package/lib/public/pane-assets/chunk-PO2JOHHW.js +1 -0
  129. package/lib/public/pane-assets/chunk-UMGHINOT.js +1 -0
  130. package/lib/public/pane-assets/manifest.json +553 -0
  131. package/lib/public/pane-assets/style-63EZCREE.css +1 -0
  132. package/lib/public/style.css +5 -1
  133. package/lib/server-owner-ui.js +6 -2
  134. package/lib/server-owner-view-observation.js +34 -0
  135. package/lib/server-palette.js +1 -0
  136. package/lib/server-pane-bootstrap.js +24 -0
  137. package/lib/server-settings.js +2 -0
  138. package/lib/server-static-assets.js +52 -0
  139. package/lib/server-workbench-layout-settings.js +56 -0
  140. package/lib/server.js +6 -45
  141. package/lib/session-activity-state.js +28 -0
  142. package/lib/session-list-delivery.js +17 -0
  143. package/lib/session-pair-turn-control.js +1 -6
  144. package/lib/session-search.js +3 -0
  145. package/lib/session-split-group-anchors.js +2 -2
  146. package/lib/session-split-group-roles.js +5 -8
  147. package/lib/session-split-group-v2-store.js +0 -3
  148. package/lib/sessions.js +16 -6
  149. package/lib/split-view-layout.js +132 -0
  150. package/lib/users-workbench-layout-preferences.js +33 -0
  151. package/lib/users.js +3 -0
  152. package/lib/workspace-query-service.js +1 -5
  153. package/lib/ws-schema.js +16 -0
  154. package/package.json +6 -3
@@ -48,7 +48,7 @@ export function createPermissionControl(className, ariaLabel) {
48
48
  control.innerHTML = '<div class="session-permission-segmented">' +
49
49
  '<button type="button" data-permission-mode="default" aria-label="Ask permissions" title="Ask permissions"><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
50
50
  '<button type="button" data-permission-mode="auto" aria-label="Auto permissions" title="Auto permissions"><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
51
- '<button type="button" data-permission-mode="bypassPermissions" aria-label="Skip permissions" title="Skip permissions"><span class="permission-label">Skip</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
51
+ '<button type="button" data-permission-mode="bypassPermissions" aria-label="YOLO permissions" title="YOLO permissions"><span class="permission-label">YOLO</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
52
52
  '</div><span class="session-permission-status" aria-live="polite"></span>';
53
53
  control.querySelector(".session-permission-status").setAttribute("aria-atomic", "true");
54
54
  return control;
@@ -56,12 +56,12 @@ export function createPermissionControl(className, ariaLabel) {
56
56
 
57
57
  function modeLabel(mode) {
58
58
  var labels = { default: "Ask", plan: "Plan", acceptEdits: "Auto-accept edits", dontAsk: "Don't ask",
59
- bypassPermissions: "Skip permissions", auto: "Auto" };
59
+ bypassPermissions: "YOLO permissions", auto: "Auto" };
60
60
  return labels[mode] || mode;
61
61
  }
62
62
 
63
63
  export function permissionStatusText(state, pending, autoSupported, autoVisible) {
64
- if (state.globalPermissionModeForced) return "Skip permissions forced by the server setting";
64
+ if (state.globalPermissionModeForced) return "YOLO permissions forced by the server setting";
65
65
  if (pending) return "Applying…";
66
66
  if (state.permissionMode === "plan") return "Plan workflow";
67
67
  if (state.permissionMode === "acceptEdits") return "Auto-accept edits workflow";
@@ -75,7 +75,7 @@ export function permissionStatusText(state, pending, autoSupported, autoVisible)
75
75
  }
76
76
 
77
77
  function compactPermissionStatusText(state, pending, autoSupported, autoVisible, fullText) {
78
- if (state.globalPermissionModeForced) return "Forced Skip";
78
+ if (state.globalPermissionModeForced) return "Forced YOLO";
79
79
  if (pending) return "Applying…";
80
80
  if (state.permissionMode === "plan") return "Plan";
81
81
  if (state.permissionMode === "acceptEdits") return "Auto-accept";
@@ -102,7 +102,7 @@ export function renderPermissionControl(control, state) {
102
102
  control.classList.toggle("permission-globally-forced", globallyForced);
103
103
  control.classList.toggle("permission-runtime-fallback", state.permissionMode === "auto" && !!state.effectivePermissionMode && state.effectivePermissionMode !== "auto");
104
104
  control.setAttribute("aria-busy", pending ? "true" : "false");
105
- var buttonTitles = { default: "Ask permissions", auto: "Auto permissions", bypassPermissions: "Skip permissions" };
105
+ var buttonTitles = { default: "Ask permissions", auto: "Auto permissions", bypassPermissions: "YOLO permissions" };
106
106
  var buttons = control.querySelectorAll("[data-permission-mode]");
107
107
  for (var i = 0; i < buttons.length; i++) {
108
108
  var mode = buttons[i].dataset.permissionMode;
@@ -147,7 +147,7 @@ export function bindPermissionControl(control, getState) {
147
147
  export function sendPermissionMode(projectSlug, sessionId, mode) {
148
148
  var ws = getWs();
149
149
  if (store.get('skipPermsEnabled') === true) {
150
- showToast("Skip permissions is forced by the server setting.", "error");
150
+ showToast("YOLO permissions are forced by the server setting.", "error");
151
151
  return false;
152
152
  }
153
153
  if (!projectSlug || !sessionId || !ws || ws.readyState !== 1) {
@@ -0,0 +1,244 @@
1
+ // Popup transcript presentation hooks. Tool identity, lifecycle and evidence
2
+ // stay owned by tools.js; this module only controls the compact activity view.
3
+ import { iconHtml } from './icons.js';
4
+ import { syncToolGroupDisclosure } from './clay-tool-activity.js';
5
+
6
+ // Mounted transcript nodes own completed records without retaining removed history.
7
+ var mountedToolGroups = new WeakMap();
8
+ export function rememberMountedToolGroup(group) { mountedToolGroups.set(group.el, group); }
9
+ export function mountedToolGroup(element) { return mountedToolGroups.get(element) || null; }
10
+ export function transcriptToolGroups(root) {
11
+ return root ? Array.from(root.querySelectorAll('.tool-group')).map(mountedToolGroup).filter(function (group) { return !!group; }) : [];
12
+ }
13
+
14
+ export function setPopupChatRendering(enabled) {
15
+ if (typeof document === 'undefined' || !document.body) return false;
16
+ document.body.classList.toggle('popup-chat-rendering', enabled === true);
17
+ if (!enabled) {
18
+ var rows = document.querySelectorAll('.popup-chat-activity-row[hidden]');
19
+ for (var i = 0; i < rows.length; i++) rows[i].hidden = false;
20
+ var groups = document.querySelectorAll('.popup-chat-activity');
21
+ for (var j = 0; j < groups.length; j++) {
22
+ var items = groups[j].querySelector('.tool-group-items');
23
+ if (items) {
24
+ var expanded = !groups[j].classList.contains('collapsed');
25
+ items.inert = !expanded;
26
+ items.setAttribute('aria-hidden', String(!expanded));
27
+ }
28
+ }
29
+ }
30
+ return enabled === true;
31
+ }
32
+
33
+ export function shouldEnablePopupChatRendering(body, state, frame, identity) {
34
+ return !!(body && body.classList && body.classList.contains('contextual-chat-pane') && state && state.paneMode && frame &&
35
+ identity && identity.kind === 'mate' && identity.mateId && identity.mateId === frame.dataset.mateId &&
36
+ state.myUserId && state.sessionOriginId && state.paneSessionId && state.activeSessionId === state.paneSessionId &&
37
+ (!frame.dataset.expectedSessionOrigin || frame.dataset.expectedSessionOrigin === state.sessionOriginId));
38
+ }
39
+
40
+ export function popupChatContextIsReady(body, state, frame, identity, surface) {
41
+ if (!body || !body.classList || !body.classList.contains('contextual-chat-pane')) return false;
42
+ if (!state || !state.paneMode || !state.connected || !state.myUserId || !state.contextualSessionReady) return false;
43
+ if (!state.sessionOriginId || state.activeSessionId !== state.paneSessionId) return false;
44
+ if (!frame || !frame.dataset || !frame.dataset.mateId || frame.dataset.ownerId !== state.myUserId) return false;
45
+ if (frame.hidden || frame.inert || !surface || surface.dataset.mode !== 'popover' || !surface.contains(frame)) return false;
46
+ if (String(frame.dataset.sessionId || '') !== String(state.paneSessionId || '')) return false;
47
+ if (frame.dataset.expectedSessionOrigin && frame.dataset.expectedSessionOrigin !== state.sessionOriginId) return false;
48
+ if (state.activeProjectMateId !== frame.dataset.mateId) return false;
49
+ if (!identity || identity.kind !== 'mate' || identity.mateId !== frame.dataset.mateId) return false;
50
+ return true;
51
+ }
52
+
53
+ export function popupChatRenderingEnabled(identity) {
54
+ return !!(identity && identity.kind === 'mate' && typeof document !== 'undefined' &&
55
+ document.body && document.body.classList.contains('popup-chat-rendering'));
56
+ }
57
+
58
+ export function popupChatRenderingActive() {
59
+ return typeof document !== 'undefined' && document.body && document.body.classList.contains('popup-chat-rendering');
60
+ }
61
+
62
+ export function reconcilePopupToolGroups(groups, active, activate, deactivate) {
63
+ for (var i = 0; i < groups.length; i++) {
64
+ var group = groups[i];
65
+ if (!group) continue;
66
+ if (active) {
67
+ group.popupChat = true;
68
+ activate(group);
69
+ } else if (group.popupChat) {
70
+ deactivate(group);
71
+ group.popupChat = false;
72
+ }
73
+ }
74
+ }
75
+
76
+ function decorateTool(tool) {
77
+ if (!tool || !tool.el || tool.popupOriginalName !== undefined) return;
78
+ var name = tool.el.querySelector('.tool-name');
79
+ tool.popupOriginalName = name.textContent;
80
+ tool.popupOriginalHidden = tool.el.hidden;
81
+ tool.popupOriginalInert = tool.el.inert;
82
+ tool.el.classList.add('popup-chat-activity-row');
83
+ // Native descriptions own reference nodes and their handlers throughout mode changes.
84
+ if (!tool.clayActivity) {
85
+ tool.popupPresentedName = popupToolActionLabel(tool.name);
86
+ name.textContent = tool.popupPresentedName;
87
+ }
88
+ }
89
+
90
+ function decorateGroup(group) {
91
+ if (!group || !group.el || group.popupOriginalState) return;
92
+ var header = group.el.querySelector('.tool-group-header');
93
+ var label = group.el.querySelector('.tool-group-label');
94
+ group.popupOriginalState = {
95
+ label: label.textContent,
96
+ collapsed: group.el.classList.contains('collapsed'),
97
+ userToggled: !!group.userToggled,
98
+ groupDisplay: group.el.style.display,
99
+ headerDisplay: header.style.display,
100
+ headerAriaExpanded: header.getAttribute('aria-expanded'),
101
+ headerAriaLabel: header.getAttribute('aria-label'),
102
+ headerTitle: header.title
103
+ };
104
+ group.el.classList.add('popup-chat-activity');
105
+ var icon = document.createElement('span');
106
+ icon.className = 'popup-chat-activity-icon';
107
+ icon.setAttribute('aria-hidden', 'true');
108
+ icon.innerHTML = iconHtml('activity');
109
+ var heading = document.createElement('span');
110
+ heading.className = 'popup-chat-activity-heading';
111
+ heading.textContent = 'Activity';
112
+ label.textContent = '';
113
+ label.appendChild(heading);
114
+ var count = document.createElement('span');
115
+ count.className = 'popup-chat-activity-count';
116
+ count.setAttribute('aria-label', '0 recorded events');
117
+ header.insertBefore(icon, label);
118
+ header.insertBefore(count, header.querySelector('.tool-group-status-icon'));
119
+ for (var i = 0; i < group.toolIds.length; i++) decorateTool(group.toolRecords[group.toolIds[i]]);
120
+ }
121
+
122
+ function restoreGroup(group) {
123
+ if (!group || !group.el) return;
124
+ for (var i = 0; i < group.toolIds.length; i++) {
125
+ var tool = group.toolRecords[group.toolIds[i]];
126
+ if (!tool || !tool.el || tool.popupOriginalName === undefined) continue;
127
+ var name = tool.el.querySelector('.tool-name');
128
+ if (!tool.clayActivity && name.textContent === tool.popupPresentedName) name.textContent = tool.popupOriginalName;
129
+ tool.el.hidden = tool.popupOriginalHidden;
130
+ tool.el.inert = tool.popupOriginalInert;
131
+ tool.el.classList.remove('popup-chat-activity-row');
132
+ delete tool.popupOriginalName;
133
+ delete tool.popupPresentedName;
134
+ delete tool.popupOriginalHidden;
135
+ delete tool.popupOriginalInert;
136
+ }
137
+ var original = group.popupOriginalState;
138
+ group.popupOriginalState = null;
139
+ if (!original) return;
140
+ var label = group.el.querySelector('.tool-group-label');
141
+ var header = group.el.querySelector('.tool-group-header');
142
+ label.textContent = original.label;
143
+ group.el.style.display = original.groupDisplay;
144
+ header.style.display = original.headerDisplay;
145
+ group.userToggled = original.userToggled;
146
+ group.el.classList.toggle('collapsed', original.collapsed);
147
+ if (original.headerAriaLabel === null) header.removeAttribute('aria-label');
148
+ else header.setAttribute('aria-label', original.headerAriaLabel);
149
+ header.title = original.headerTitle;
150
+ var icon = header.querySelector('.popup-chat-activity-icon');
151
+ var count = header.querySelector('.popup-chat-activity-count');
152
+ if (icon) icon.remove();
153
+ if (count) count.remove();
154
+ group.el.classList.remove('popup-chat-activity');
155
+ syncToolGroupDisclosure(group.el);
156
+ if (original.headerAriaExpanded !== null) header.setAttribute('aria-expanded', original.headerAriaExpanded);
157
+ }
158
+
159
+ export function syncPopupChatToolGroups(enabled, groups, identity) {
160
+ var active = !!(enabled && popupChatRenderingActive() && identity && identity.kind === 'mate');
161
+ reconcilePopupToolGroups(groups, active, function (group) {
162
+ decorateGroup(group);
163
+ for (var i = 0; i < group.toolIds.length; i++) decorateTool(group.toolRecords[group.toolIds[i]]);
164
+ group.el.style.display = '';
165
+ group.el.querySelector('.tool-group-header').style.display = '';
166
+ if (!group.userToggled) group.el.classList.add('collapsed');
167
+ syncToolGroupDisclosure(group.el);
168
+ syncPopupActivityGroup(group);
169
+ }, restoreGroup);
170
+ }
171
+
172
+ function hasIndependentControl(row) {
173
+ if (!row || !row.querySelectorAll) return false;
174
+ var controls = row.querySelectorAll('button, a[href], input, select, textarea, [role="button"], [role="menuitem"], [role="option"]');
175
+ var disclosure = row.querySelector('.tool-header');
176
+ if (row.querySelector('.tool-desc-link')) return true;
177
+ for (var i = 0; i < controls.length; i++) {
178
+ if (controls[i] !== disclosure) return true;
179
+ }
180
+ return false;
181
+ }
182
+
183
+ export function syncPopupActivityGroup(group) {
184
+ if (!group || !group.popupChat || !group.el) return false;
185
+ var header = group.el.querySelector('.tool-group-header');
186
+ var items = group.el.querySelector('.tool-group-items');
187
+ var count = group.el.querySelector('.popup-chat-activity-count');
188
+ if (!header || !items) return false;
189
+
190
+ var expanded = !group.el.classList.contains('collapsed');
191
+ var rows = items.children;
192
+ var firstVisible = Math.max(0, rows.length - 2);
193
+ var pinned = 0;
194
+ for (var i = 0; i < rows.length; i++) {
195
+ var row = rows[i];
196
+ var keepVisible = hasIndependentControl(row);
197
+ if (keepVisible) pinned++;
198
+ row.classList.add('popup-chat-activity-row');
199
+ row.hidden = !expanded && i < firstVisible && !keepVisible;
200
+ row.inert = row.hidden;
201
+ }
202
+
203
+ // The native group disclosure remains the one control for revealing older
204
+ // activity. In popup mode the visible recent rows stay reachable even while
205
+ // the rest of the group is collapsed.
206
+ items.inert = false;
207
+ items.setAttribute('aria-hidden', 'false');
208
+ header.setAttribute('aria-expanded', String(expanded));
209
+ header.setAttribute('aria-label', 'Activity, ' + group.toolCount + ' recorded events. ' + (expanded ? 'Hide older activity' : 'Show older activity'));
210
+ header.title = expanded ? 'Hide older activity' : 'Show older activity';
211
+ if (count) {
212
+ count.textContent = String(group.toolCount);
213
+ count.setAttribute('aria-label', group.toolCount + ' recorded events');
214
+ count.dataset.pinnedCount = String(pinned);
215
+ }
216
+ return true;
217
+ }
218
+
219
+ export function popupToolActionLabel(name) {
220
+ var labels = {
221
+ Read: 'Read', Edit: 'Edit', Write: 'Write', Bash: 'Command',
222
+ Grep: 'Search files', Glob: 'Find files', WebSearch: 'Web search',
223
+ WebFetch: 'Fetch page', Task: 'Task'
224
+ };
225
+ return labels[name] || name || 'Tool activity';
226
+ }
227
+
228
+ export function popupToolDescription(name, input) {
229
+ input = input && typeof input === 'object' ? input : {};
230
+ if (name === 'Read' || name === 'Edit' || name === 'Write') {
231
+ var filePath = typeof input.file_path === 'string' ? input.file_path : '';
232
+ var parts = filePath.split('/');
233
+ return parts.length > 3 ? '…/' + parts.slice(-3).join('/') : filePath;
234
+ }
235
+ if (name === 'Bash') return typeof input.description === 'string' ? input.description.slice(0, 100) : '';
236
+ if (name === 'Grep') return typeof input.pattern === 'string' ? input.pattern.slice(0, 100) : '';
237
+ if (name === 'Glob') return typeof input.pattern === 'string' ? input.pattern.slice(0, 100) : '';
238
+ if (name === 'WebSearch') return typeof input.query === 'string' ? input.query.slice(0, 100) : '';
239
+ if (name === 'WebFetch') {
240
+ try { return new URL(input.url).hostname; } catch (error) { return ''; }
241
+ }
242
+ if (name === 'Task') return typeof input.description === 'string' ? input.description.slice(0, 100) : '';
243
+ return '';
244
+ }
@@ -70,7 +70,7 @@ export function isProjectSessionReady(state, socket) {
70
70
  }
71
71
 
72
72
  export function splitPanesMatchProject(split, slug) {
73
- if (!split || !Array.isArray(split.panes) || split.panes.length < 2 || split.panes.length > 3 || !slug) return false;
73
+ if (!split || !Array.isArray(split.panes) || split.panes.length < 2 || !slug) return false;
74
74
  for (var i = 0; i < split.panes.length; i++) if (split.panes[i].slug !== slug) return false;
75
75
  return true;
76
76
  }
@@ -78,7 +78,7 @@ export function splitPanesMatchProject(split, slug) {
78
78
  export function hasCurrentSplitGroup(split, groups) {
79
79
  if (!split || !split.groupId) return true;
80
80
  var members = split.panes && split.panes.map(function (pane) { return pane.sessionId; });
81
- if (!members || members.length < 2 || members.length > 3) return false;
81
+ if (!members || members.length < 2) return false;
82
82
  var available = Array.isArray(groups) ? groups : [];
83
83
  for (var i = 0; i < available.length; i++) {
84
84
  var group = available[i];
@@ -1,4 +1,6 @@
1
- import { copyToClipboard } from './utils.js';
1
+ import { ensurePaneFeatureAssets } from './pane-feature-assets.js';
2
+ import { store } from './store.js';
3
+ import { showToast, copyToClipboard } from './utils.js';
2
4
 
3
5
  var activeShareUrl = null;
4
6
  var activeShareTitle = null;
@@ -32,6 +34,15 @@ export function triggerShare(options) {
32
34
  return;
33
35
  }
34
36
 
37
+ if (typeof qrcode === 'undefined') {
38
+ var owner = store.get('myUserId'), slug = store.get('currentSlug'), generation = store.get('projectSplitViewSocketGeneration');
39
+ ensurePaneFeatureAssets('qr').then(function () {
40
+ if (owner === store.get('myUserId') && slug === store.get('currentSlug') && generation === store.get('projectSplitViewSocketGeneration')) triggerShare(options);
41
+ }).catch(function (error) {
42
+ if (owner === store.get('myUserId') && slug === store.get('currentSlug') && generation === store.get('projectSplitViewSocketGeneration')) showToast(error.message, 'error');
43
+ });
44
+ return;
45
+ }
35
46
  // Show QR overlay
36
47
  var qrOverlay = document.getElementById("qr-overlay");
37
48
  var qrCanvas = document.getElementById("qr-canvas");
@@ -1,8 +1,73 @@
1
1
  // Single arbitration point for the mutually exclusive right-side workbench.
2
2
 
3
3
  import { store } from './store.js';
4
+ import { iconHtml, refreshIcons } from './icons.js';
5
+ import { showToast } from './utils.js';
4
6
 
5
7
  var closers = {};
8
+ var panelIds = { terminal: 'terminal-container', browser: 'shared-browser-panel', 'file-viewer': 'file-viewer',
9
+ 'project-logs': 'project-logs-panel', you: 'you-panel', issues: 'issues-panel', linear: 'linear-panel',
10
+ 'notes-browser': 'notes-browser', 'scheduled-tasks': 'scheduled-tasks-panel', debates: 'debates-workbench',
11
+ 'mcp-skills': 'mcp-skills-workbench', 'mate-knowledge': 'mate-knowledge-workbench', wireframe: 'wireframe-panel' };
12
+
13
+ // Pane startup does not need account settings for a closed workbench.
14
+ function ensureLayoutPreference() {
15
+ var account = store.get('myUserId'), project = store.get('currentSlug');
16
+ return import('./workbench-layout-preference.js').then(function (api) {
17
+ if (account !== store.get('myUserId') || project !== store.get('currentSlug')) return false;
18
+ return api.ensureWorkbenchLayoutPreference();
19
+ }).catch(function () {
20
+ if (account !== store.get('myUserId') || project !== store.get('currentSlug')) return false;
21
+ store.set({ workbenchLayoutPreference: Object.assign({}, store.get('workbenchLayoutPreference') || {}, {
22
+ loading: false, saving: false, error: 'Workbench layout could not be loaded. Try again.'
23
+ }) });
24
+ return false;
25
+ });
26
+ }
27
+
28
+ function renderLayoutControls() {
29
+ if (typeof document === 'undefined') return;
30
+ var main = document.getElementById('main-panels');
31
+ if (!main) return;
32
+ var state = store.get('workbenchLayoutPreference') || {}, docked = state.mode === 'docked';
33
+ main.dataset.workbenchMode = docked ? 'docked' : 'overlay';
34
+ var panel = document.getElementById(panelIds[store.get('rightWorkbenchOwner')]);
35
+ if (!panel) return;
36
+ var header = panel.querySelector(':scope > header, :scope > .file-viewer-header .file-viewer-tabbar, :scope > .terminal-header');
37
+ if (!header) return;
38
+ var button = panel.querySelector('.workbench-layout-toggle');
39
+ if (!button) {
40
+ var actions = header.querySelector('[class*="window-actions"], .terminal-header-actions, .shared-browser-window, :scope.mcp-skills-header > div') || header;
41
+ button = document.createElement('button'); button.type = 'button'; button.className = 'workbench-layout-toggle';
42
+ button.addEventListener('click', function () {
43
+ var current = store.get('workbenchLayoutPreference') || {};
44
+ if (!current.ready || current.loading || current.saving) { ensureLayoutPreference(); return; }
45
+ var owner = store.get('myUserId'), project = store.get('currentSlug');
46
+ function currentTarget() { return button.isConnected && owner === store.get('myUserId') && project === store.get('currentSlug'); }
47
+ import('./workbench-layout-preference.js').then(function (api) {
48
+ var fresh = store.get('workbenchLayoutPreference') || {};
49
+ if (!currentTarget() || fresh.mode !== current.mode || fresh.revision !== current.revision || fresh.loading || fresh.saving || !fresh.ready) return false;
50
+ return api.saveWorkbenchLayoutMode(current.mode === 'docked' ? 'overlay' : 'docked');
51
+ }).then(function (saved) {
52
+ if (saved === false && currentTarget()) showToast((store.get('workbenchLayoutPreference') || {}).error || 'Workbench layout could not be saved. Try again.', 'error');
53
+ }).catch(function (error) {
54
+ if (currentTarget()) showToast(error.message || 'Workbench layout could not be saved. Try again.', 'error');
55
+ });
56
+ });
57
+ var anchor = actions === header ? header.querySelector('[data-knowledge-wide]') : actions.firstElementChild;
58
+ actions.insertBefore(button, anchor || null);
59
+ }
60
+ if (button.dataset.mode !== (docked ? 'docked' : 'overlay')) {
61
+ button.dataset.mode = docked ? 'docked' : 'overlay';
62
+ button.innerHTML = iconHtml(docked ? 'layers-2' : 'panel-right');
63
+ refreshIcons();
64
+ }
65
+ button.setAttribute('aria-label', docked ? 'Float workbench over sessions' : 'Dock workbench beside sessions');
66
+ button.title = state.error || button.getAttribute('aria-label');
67
+ button.setAttribute('aria-pressed', docked ? 'true' : 'false');
68
+ button.disabled = !!state.loading || !!state.saving;
69
+ button.setAttribute('aria-busy', state.loading || state.saving ? 'true' : 'false');
70
+ }
6
71
 
7
72
  export function registerRightWorkbench(name, close) {
8
73
  if (!name || typeof close !== "function") return;
@@ -10,6 +75,7 @@ export function registerRightWorkbench(name, close) {
10
75
  }
11
76
 
12
77
  export function claimRightWorkbench(name) {
78
+ if (typeof document !== 'undefined') ensureLayoutPreference();
13
79
  var previousRevision = store.get("rightWorkbenchRevision") || 0;
14
80
  var nextRevision = previousRevision + 1;
15
81
  storeSet(name, nextRevision);
@@ -18,6 +84,7 @@ export function claimRightWorkbench(name) {
18
84
  if (keys[i] === name) continue;
19
85
  try { closers[keys[i]](); } catch (error) { /* one panel must not block another */ }
20
86
  }
87
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(renderLayoutControls);
21
88
  return nextRevision;
22
89
  }
23
90
 
@@ -39,6 +106,7 @@ export function resetRightWorkbench() {
39
106
  }
40
107
 
41
108
  store.subscribe(function (state, previous) {
109
+ if (state.workbenchLayoutPreference !== previous.workbenchLayoutPreference || state.rightWorkbenchOwner !== previous.rightWorkbenchOwner) renderLayoutControls();
42
110
  if (state.currentSlug === previous.currentSlug && state.myUserId === previous.myUserId) return;
43
111
  resetRightWorkbench();
44
112
  });
@@ -0,0 +1,79 @@
1
+ import { store } from './store.js';
2
+
3
+ export var SEARCH_HINT_INTERVAL = 180000;
4
+ export var SEARCH_HINT_QUESTIONS = Object.freeze([
5
+ 'What was I working on recently?',
6
+ 'Where did I leave off yesterday?',
7
+ 'What did I review recently?',
8
+ "Find the session I said I'd return to.",
9
+ 'What work did I leave unfinished?',
10
+ 'What did we decide about this project?',
11
+ 'Why did we choose this approach?',
12
+ 'Find the conversation about this bug.',
13
+ 'Which sessions need my attention?',
14
+ "What's happening across this view?",
15
+ 'What are my Workers doing?',
16
+ 'Which sessions are waiting for approval?'
17
+ ]);
18
+ export function searchHintText(index) {
19
+ return 'Search or ask Clay, like “' + SEARCH_HINT_QUESTIONS[index % SEARCH_HINT_QUESTIONS.length] + '”';
20
+ }
21
+
22
+ var trigger = null, input = null, timer = null, unsubscribe = null;
23
+ function relevantFocus(target) { return !!target && (target === input || trigger && trigger.contains(target)); }
24
+ function available() {
25
+ return trigger && input && !document.hidden && !store.get('paneMode') && window === window.top &&
26
+ !store.get('searchHintsPaletteOpen') && !store.get('searchHintsHovered') && !store.get('searchHintsFocused');
27
+ }
28
+ function cancelTimer() { if (timer !== null) { clearTimeout(timer); timer = null; } }
29
+ function paint() {
30
+ var label = trigger.querySelector('.cmd-palette-searchbar-text');
31
+ var text = searchHintText(store.get('searchHintIndex') || 0);
32
+ if (label) label.textContent = text;
33
+ input.placeholder = text;
34
+ }
35
+ function schedule() {
36
+ if (!available()) { cancelTimer(); return; }
37
+ if (timer !== null) return;
38
+ timer = setTimeout(function () {
39
+ timer = null;
40
+ if (!available()) return;
41
+ store.set({ searchHintIndex: ((store.get('searchHintIndex') || 0) + 1) % SEARCH_HINT_QUESTIONS.length });
42
+ paint(); schedule();
43
+ }, SEARCH_HINT_INTERVAL);
44
+ }
45
+ function visibilityChanged() { cancelTimer(); schedule(); }
46
+ function enter() { store.set({ searchHintsHovered: true }); }
47
+ function leave() { store.set({ searchHintsHovered: false }); }
48
+ function focusIn(event) { store.set({ searchHintsFocused: relevantFocus(event.target) }); }
49
+ function focusOut(event) { store.set({ searchHintsFocused: relevantFocus(event.relatedTarget) }); }
50
+ export function setSearchHintsPaletteOpen(open) { store.set({ searchHintsPaletteOpen: open === true }); }
51
+ export function disposeSearchHints() {
52
+ cancelTimer();
53
+ if (!trigger) return;
54
+ trigger.removeEventListener('mouseenter', enter); trigger.removeEventListener('mouseleave', leave);
55
+ document.removeEventListener('focusin', focusIn); document.removeEventListener('focusout', focusOut);
56
+ document.removeEventListener('visibilitychange', visibilityChanged);
57
+ if (unsubscribe) unsubscribe();
58
+ trigger = null; input = null; unsubscribe = null;
59
+ }
60
+ export function initSearchHints(button, field) {
61
+ if (button) button.removeAttribute('title');
62
+ if (trigger === button && input === field) return;
63
+ disposeSearchHints();
64
+ if (!button || !field) return;
65
+ trigger = button; input = field;
66
+ store.set({ searchHintIndex: store.get('searchHintIndex') || 0, searchHintsPaletteOpen: false,
67
+ searchHintsHovered: trigger.matches(':hover'), searchHintsFocused: relevantFocus(document.activeElement) });
68
+ if (!store.get('searchHintsFocused')) paint();
69
+ trigger.addEventListener('mouseenter', enter); trigger.addEventListener('mouseleave', leave);
70
+ document.addEventListener('focusin', focusIn); document.addEventListener('focusout', focusOut);
71
+ document.addEventListener('visibilitychange', visibilityChanged);
72
+ unsubscribe = store.subscribe(function (state, previous) {
73
+ if (state.paneMode !== previous.paneMode || state.searchHintsPaletteOpen !== previous.searchHintsPaletteOpen ||
74
+ state.searchHintsHovered !== previous.searchHintsHovered || state.searchHintsFocused !== previous.searchHintsFocused) {
75
+ cancelTimer(); schedule();
76
+ }
77
+ });
78
+ schedule();
79
+ }
@@ -22,6 +22,7 @@ export function closeSessionContextMenu(restoreFocus) {
22
22
  document.removeEventListener("keydown", onMenuKey, true);
23
23
  window.removeEventListener("blur", onWindowBlur);
24
24
  if (state.anchor) state.anchor.setAttribute("aria-expanded", "false");
25
+ state.el.hidden = true;
25
26
  if (state.el && state.el.isConnected) state.el.remove();
26
27
  if (restoreFocus !== false && state.anchor && state.anchor.isConnected) {
27
28
  state.anchor.focus({ preventScroll: true });
@@ -54,10 +55,40 @@ function onMenuKey(event) {
54
55
  event.preventDefault();
55
56
  event.stopPropagation();
56
57
  closeSessionContextMenu(true);
58
+ return;
59
+ }
60
+ var items = Array.from(state.el.querySelectorAll('button:not([disabled])')), index = items.indexOf(document.activeElement), next = null;
61
+ if (!items.length) return;
62
+ if (event.key === 'ArrowDown' || event.key === 'Tab') next = (index + (event.shiftKey ? -1 : 1) + items.length) % items.length;
63
+ else if (event.key === 'ArrowUp') next = (index - 1 + items.length) % items.length;
64
+ else if (event.key === 'Home') next = 0;
65
+ else if (event.key === 'End') next = items.length - 1;
66
+ if (next !== null) { event.preventDefault(); event.stopPropagation(); items[next].focus({ preventScroll: true }); }
67
+ }
68
+
69
+ export function positionSessionContextMenu(menu, anchor, point) {
70
+ var rect = anchor.getBoundingClientRect(), box = menu.getBoundingClientRect();
71
+ var x = point ? point.x : rect.left, y = point ? point.y : rect.bottom + 2;
72
+ menu.style.left = Math.max(8, Math.min(x, window.innerWidth - box.width - 8)) + 'px';
73
+ menu.style.top = Math.max(8, Math.min(y, window.innerHeight - box.height - 8)) + 'px';
74
+ menu.style.right = 'auto';
75
+ }
76
+
77
+ export function reconcileSessionContextMenu() {
78
+ var state = menuState(); if (!state) return;
79
+ var anchor = state.resolveAnchor ? state.resolveAnchor() : state.anchor;
80
+ if (!anchor || !anchor.isConnected || state.slug !== store.get('currentSlug') || state.viewerId !== store.get('myUserId') || state.ws !== getWs()) {
81
+ closeSessionContextMenu(false); return;
57
82
  }
83
+ if (anchor === state.anchor) return;
84
+ state.anchor.setAttribute('aria-expanded', 'false');
85
+ var hadTabIndex = anchor.hasAttribute('tabindex'), tabIndex = anchor.getAttribute('tabindex');
86
+ anchor.setAttribute('aria-haspopup', 'menu'); anchor.setAttribute('aria-expanded', 'true');
87
+ if (!hadTabIndex) anchor.setAttribute('tabindex', '-1');
88
+ store.set({ sessionContextMenu: Object.assign({}, state, { anchor: anchor, anchorHadTabIndex: hadTabIndex, anchorTabIndex: tabIndex }) });
58
89
  }
59
90
 
60
- export function mountSessionContextMenu(menu, anchor, sessionId) {
91
+ export function mountSessionContextMenu(menu, anchor, sessionId, options) {
61
92
  closeSessionContextMenu(false);
62
93
  menu.setAttribute("role", "menu");
63
94
  var items = menu.querySelectorAll("button");
@@ -76,6 +107,8 @@ export function mountSessionContextMenu(menu, anchor, sessionId) {
76
107
  anchorHadTabIndex: anchorHadTabIndex,
77
108
  anchorTabIndex: anchorTabIndex,
78
109
  sessionId: sessionId || null,
110
+ resolveAnchor: options && options.resolveAnchor || null,
111
+ slug: store.get('currentSlug'), viewerId: store.get('myUserId'), ws: getWs(),
79
112
  } });
80
113
  document.addEventListener("pointerdown", onOutsidePress, true);
81
114
  document.addEventListener("mousedown", onOutsidePress, true);
@@ -205,7 +238,7 @@ export function startHeaderSessionInlineRename(title, control, session) {
205
238
  }
206
239
 
207
240
  store.subscribe(function (state, previous) {
208
- if (state.currentSlug !== previous.currentSlug || state.dmMode !== previous.dmMode ||
241
+ if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.dmMode !== previous.dmMode ||
209
242
  state.sessionActionEpoch !== previous.sessionActionEpoch || state.panePendingBinding !== previous.panePendingBinding ||
210
243
  (previous.connected && !state.connected)) {
211
244
  closeSessionContextMenu(false);
@@ -89,7 +89,8 @@ function setAccessibleState(root, body, toggle, open) {
89
89
  }
90
90
 
91
91
  function finish(key, token, root, body, toggle, open) {
92
- if (currentRecord(key, token)) putRecord(key, null);
92
+ if (!currentRecord(key, token) || !root.isConnected) return;
93
+ putRecord(key, null);
93
94
  cleanBody(body);
94
95
  setAccessibleState(root, body, toggle, open);
95
96
  body.hidden = !open;