clay-server 5.0.2-beta.1 → 5.1.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 (162) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/lib/daemon-project-access.js +7 -1
  4. package/lib/daemon.js +2 -0
  5. package/lib/dicebear-avatar.js +62 -0
  6. package/lib/knowledge-db-service.js +175 -0
  7. package/lib/knowledge-db-validation.js +0 -0
  8. package/lib/knowledge-document-index.js +49 -0
  9. package/lib/knowledge-draft-store.js +56 -0
  10. package/lib/knowledge-file-store.js +199 -0
  11. package/lib/knowledge-markdown.js +128 -0
  12. package/lib/mate-knowledge-mcp-server.js +29 -6
  13. package/lib/mate-knowledge-service.js +23 -2
  14. package/lib/mate-knowledge-sync.js +36 -21
  15. package/lib/mate-workspace-color.js +4 -0
  16. package/lib/mates.js +18 -0
  17. package/lib/project-connection.js +36 -2
  18. package/lib/project-debate-proposal.js +6 -0
  19. package/lib/project-debate-workspace.js +43 -0
  20. package/lib/project-debate.js +1 -0
  21. package/lib/project-file-path.js +2 -3
  22. package/lib/project-knowledge.js +315 -136
  23. package/lib/project-mate-creation-proposal.js +1 -0
  24. package/lib/project-request-access.js +13 -1
  25. package/lib/project-sessions.js +39 -2
  26. package/lib/project-user-knowledge.js +60 -0
  27. package/lib/project.js +59 -18
  28. package/lib/public/app.js +73 -34
  29. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  30. package/lib/public/css/clay-workspace.css +105 -0
  31. package/lib/public/css/debates-workbench.css +47 -0
  32. package/lib/public/css/filebrowser.css +10 -0
  33. package/lib/public/css/home-debate-live.css +26 -0
  34. package/lib/public/css/home-hub.css +3 -1
  35. package/lib/public/css/home-mate-model-picker.css +15 -131
  36. package/lib/public/css/home-mate-settings.css +168 -16
  37. package/lib/public/css/icon-strip.css +249 -32
  38. package/lib/public/css/mate-conversation.css +176 -0
  39. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  40. package/lib/public/css/mate-workspace-color.css +16 -0
  41. package/lib/public/css/mates.css +28 -28
  42. package/lib/public/css/messages.css +1 -114
  43. package/lib/public/css/mobile-nav.css +16 -16
  44. package/lib/public/css/overlays.css +8 -0
  45. package/lib/public/css/project-logs.css +7 -0
  46. package/lib/public/css/project-mate-creation.css +105 -0
  47. package/lib/public/css/right-workbench.css +3 -3
  48. package/lib/public/css/session-folders.css +80 -21
  49. package/lib/public/css/sidebar.css +183 -107
  50. package/lib/public/css/title-bar.css +151 -0
  51. package/lib/public/css/worker-delegation.css +47 -0
  52. package/lib/public/css/worker-proposal.css +72 -150
  53. package/lib/public/css/you.css +74 -0
  54. package/lib/public/index.html +33 -101
  55. package/lib/public/modules/account-island.js +230 -0
  56. package/lib/public/modules/app-cursors.js +19 -43
  57. package/lib/public/modules/app-debate-ui.js +4 -3
  58. package/lib/public/modules/app-header.js +106 -0
  59. package/lib/public/modules/app-message-router.js +12 -3
  60. package/lib/public/modules/app-messages.js +34 -9
  61. package/lib/public/modules/app-notifications.js +4 -7
  62. package/lib/public/modules/app-projects.js +5 -38
  63. package/lib/public/modules/app-rendering.js +12 -13
  64. package/lib/public/modules/avatar.js +25 -9
  65. package/lib/public/modules/chat-render-runtime.js +16 -4
  66. package/lib/public/modules/command-palette.js +2 -15
  67. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  68. package/lib/public/modules/debate-controls.js +299 -0
  69. package/lib/public/modules/debate.js +2 -0
  70. package/lib/public/modules/debates-workbench.js +339 -0
  71. package/lib/public/modules/default-ai.js +81 -65
  72. package/lib/public/modules/filebrowser.js +28 -8
  73. package/lib/public/modules/home-debate-controls.js +18 -289
  74. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  75. package/lib/public/modules/home-mate-color-picker.js +66 -0
  76. package/lib/public/modules/home-mate-creation.js +1 -1
  77. package/lib/public/modules/home-mate-instructions.js +90 -0
  78. package/lib/public/modules/home-mate-model-picker.js +80 -271
  79. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  80. package/lib/public/modules/home-mate-settings.js +118 -125
  81. package/lib/public/modules/home-shell.js +3 -0
  82. package/lib/public/modules/home-surface-boot.js +1 -9
  83. package/lib/public/modules/html-preview.js +19 -0
  84. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  85. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  86. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  87. package/lib/public/modules/knowledge-history-state.js +55 -0
  88. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  89. package/lib/public/modules/knowledge-preview-links.js +70 -0
  90. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  91. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  92. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  93. package/lib/public/modules/mate-knowledge.js +11 -3
  94. package/lib/public/modules/mate-management.js +54 -10
  95. package/lib/public/modules/mate-sidebar.js +11 -1
  96. package/lib/public/modules/mate-workspace-color.js +33 -0
  97. package/lib/public/modules/mention.js +5 -13
  98. package/lib/public/modules/profile.js +51 -18
  99. package/lib/public/modules/project-chat-identity.js +79 -0
  100. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  101. package/lib/public/modules/project-debate-workspace.js +185 -0
  102. package/lib/public/modules/project-header-identity.js +127 -0
  103. package/lib/public/modules/project-logs-render.js +9 -4
  104. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  105. package/lib/public/modules/project-mate-navigation.js +238 -0
  106. package/lib/public/modules/project-session-navigation.js +29 -0
  107. package/lib/public/modules/project-switcher.js +6 -8
  108. package/lib/public/modules/projects-hub-view.js +5 -6
  109. package/lib/public/modules/projects-hub.js +2 -6
  110. package/lib/public/modules/qrcode.js +27 -8
  111. package/lib/public/modules/search-clay-chat.js +8 -11
  112. package/lib/public/modules/session-activity-tooltip.js +12 -0
  113. package/lib/public/modules/session-create-form.js +86 -14
  114. package/lib/public/modules/session-create.js +50 -7
  115. package/lib/public/modules/session-list-visibility.js +41 -0
  116. package/lib/public/modules/session-presence-ui.js +4 -1
  117. package/lib/public/modules/session-title-marquee.js +51 -0
  118. package/lib/public/modules/sidebar-mates.js +3 -50
  119. package/lib/public/modules/sidebar-mobile.js +63 -60
  120. package/lib/public/modules/sidebar-projects.js +84 -97
  121. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  122. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  123. package/lib/public/modules/sidebar-sessions.js +49 -50
  124. package/lib/public/modules/sidebar.js +11 -13
  125. package/lib/public/modules/thinking-lifecycle.js +7 -6
  126. package/lib/public/modules/tool-palette-order.js +4 -3
  127. package/lib/public/modules/tool-palette.js +13 -3
  128. package/lib/public/modules/tools.js +22 -22
  129. package/lib/public/modules/worker-proposal.js +42 -8
  130. package/lib/public/modules/worker-runtime-picker.js +115 -0
  131. package/lib/public/modules/you-activity.js +121 -0
  132. package/lib/public/modules/you.js +251 -0
  133. package/lib/public/style.css +16 -8
  134. package/lib/public/vendor/codemirror/LICENSE +21 -0
  135. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  136. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  137. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  138. package/lib/public/vendor/codemirror/markdown.js +886 -0
  139. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  140. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  141. package/lib/sdk-bridge.js +15 -3
  142. package/lib/server-admin.js +11 -0
  143. package/lib/server-home-chat-events.js +1 -1
  144. package/lib/server-home-chat.js +25 -3
  145. package/lib/server-home-debate-planning.js +4 -1
  146. package/lib/server-home-debates.js +2 -0
  147. package/lib/server-home-mate-creation.js +4 -1
  148. package/lib/server-mate-instructions.js +44 -0
  149. package/lib/server-mates.js +12 -3
  150. package/lib/server-palette.js +1 -1
  151. package/lib/server-settings.js +65 -1
  152. package/lib/server-user-knowledge.js +119 -0
  153. package/lib/server.js +21 -1
  154. package/lib/session-visibility.js +7 -2
  155. package/lib/sessions.js +32 -6
  156. package/lib/user-knowledge-service.js +167 -0
  157. package/lib/users-permissions.js +7 -0
  158. package/lib/users-project-mate-filter-preferences.js +39 -0
  159. package/lib/users.js +6 -0
  160. package/lib/workspace-query-access.js +4 -1
  161. package/lib/ws-schema.js +50 -0
  162. package/package.json +10 -1
@@ -1,3 +1,4 @@
1
+ import { enhanceWorkerRuntimePicker } from './worker-runtime-picker.js';
1
2
  import { getWs } from './ws-ref.js';
2
3
  import { addToMessages, scrollToBottom, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
3
4
  import { effortLevelsFor, effortDisplayName } from './app-panels.js';
@@ -78,10 +79,12 @@ function fillEffort(card, vendor, model, msg, preferred) {
78
79
  button.type = "button";
79
80
  button.className = "worker-proposal-effort-btn" + (levels[i] === selected ? " active" : "");
80
81
  button.dataset.effort = levels[i];
82
+ button.setAttribute("aria-pressed", String(levels[i] === selected));
81
83
  button.textContent = effortDisplayName(levels[i]);
82
84
  button.addEventListener("click", function () {
83
- buttons.querySelectorAll("button").forEach(function (item) { item.classList.remove("active"); });
85
+ buttons.querySelectorAll("button").forEach(function (item) { item.classList.remove("active"); item.setAttribute("aria-pressed", "false"); });
84
86
  this.classList.add("active");
87
+ this.setAttribute("aria-pressed", "true");
85
88
  });
86
89
  buttons.appendChild(button);
87
90
  }
@@ -90,6 +93,7 @@ function fillEffort(card, vendor, model, msg, preferred) {
90
93
  function setControlsDisabled(card, disabled) {
91
94
  var controls = card.querySelectorAll("select, .worker-proposal-effort-btn, .worker-proposal-action");
92
95
  for (var i = 0; i < controls.length; i++) controls[i].disabled = disabled;
96
+ if (card._refreshPickers) card._refreshPickers();
93
97
  }
94
98
 
95
99
  function statusLabel(status) {
@@ -115,6 +119,8 @@ function decisionLabel(status, autoAccepted) {
115
119
 
116
120
  function applyState(card, msg) {
117
121
  var status = msg.status || "pending";
122
+ var previousStatus = card.dataset.status;
123
+ var focusedControl = card.contains(document.activeElement) && document.activeElement.closest(".worker-proposal-config, .worker-proposal-actions");
118
124
  card.dataset.status = status;
119
125
  if (msg.autoAccepted || msg.autoApproved) card.dataset.autoAccepted = "true";
120
126
  if (msg.autoAccepted === false || msg.autoApproved === false) card.dataset.autoAccepted = "false";
@@ -134,7 +140,18 @@ function applyState(card, msg) {
134
140
  }
135
141
  setControlsDisabled(card, status !== "pending");
136
142
  var actions = card.querySelector(".worker-proposal-actions");
137
- if (actions) actions.classList.toggle("hidden", autoAccepted);
143
+ if (actions) actions.classList.toggle("hidden", status !== "pending");
144
+ card.querySelector(".worker-proposal-config").classList.toggle("hidden", status !== "pending");
145
+ var receipt = card.querySelector(".worker-proposal-runtime");
146
+ var vendor = card.querySelector(".worker-proposal-vendor");
147
+ var model = card.querySelector(".worker-proposal-model");
148
+ var effort = card.querySelector(".worker-proposal-effort-btn.active");
149
+ receipt.textContent = [VENDOR_NAMES[vendor.value] || vendor.value, model.options[model.selectedIndex] && model.options[model.selectedIndex].textContent,
150
+ effort && !card.querySelector(".worker-proposal-effort").classList.contains("hidden") ? effort.textContent + " reasoning" : ""].filter(Boolean).join(" · ");
151
+ receipt.classList.toggle("hidden", status === "pending");
152
+ var brief = card.querySelector(".worker-proposal-brief");
153
+ if (previousStatus !== status) brief.open = status === "pending";
154
+ if (focusedControl && status !== "pending" && badge) badge.focus();
138
155
  }
139
156
 
140
157
  function sendDecision(card, accepted) {
@@ -169,7 +186,9 @@ export function renderWorkerProposal(msg) {
169
186
  var header = document.createElement("div");
170
187
  header.className = "worker-proposal-header";
171
188
  var replacing = msg.action === "replace";
172
- header.innerHTML = '<div class="worker-proposal-mark">' + iconHtml("git-branch") + '</div><div class="worker-proposal-heading"><span class="worker-proposal-kicker">DRIVER / SPLIT WORKER</span><strong>' + (replacing ? "Configure a replacement Split Worker" : "Configure a Split Worker") + '</strong></div><span class="worker-proposal-status"></span>';
189
+ header.innerHTML = '<div class="worker-proposal-mark">' + iconHtml("git-branch") + '</div><div class="worker-proposal-heading"><span class="worker-proposal-kicker">Worker proposal</span><strong>' + (replacing ? "Replace Split Worker" : "Add a Split Worker") + '</strong></div><span class="worker-proposal-status"></span>';
190
+ header.querySelector(".worker-proposal-status").setAttribute("role", "status");
191
+ header.querySelector(".worker-proposal-status").tabIndex = -1;
173
192
  card.appendChild(header);
174
193
 
175
194
  var summary = document.createElement("p");
@@ -177,6 +196,13 @@ export function renderWorkerProposal(msg) {
177
196
  summary.textContent = msg.summary || "This task can be planned here and executed in a visible Split Worker session.";
178
197
  card.appendChild(summary);
179
198
 
199
+ var brief = document.createElement("details");
200
+ brief.className = "worker-proposal-brief";
201
+ brief.open = !msg.status || msg.status === "pending";
202
+ var briefToggle = document.createElement("summary");
203
+ briefToggle.innerHTML = iconHtml("chevron-right") + "<span>Plan and recommendation</span>";
204
+ brief.appendChild(briefToggle);
205
+ card.appendChild(brief);
180
206
  var steps = planSteps(msg.plan);
181
207
  if (steps.length > 0) {
182
208
  var plan = document.createElement("ol");
@@ -186,26 +212,26 @@ export function renderWorkerProposal(msg) {
186
212
  item.textContent = steps[i];
187
213
  plan.appendChild(item);
188
214
  }
189
- card.appendChild(plan);
215
+ brief.appendChild(plan);
190
216
  }
191
217
 
192
218
  if (msg.recommendationRationale) {
193
219
  var rationale = document.createElement("div");
194
220
  rationale.className = "worker-proposal-rationale";
195
221
  var rationaleLabel = document.createElement("span");
196
- rationaleLabel.textContent = "Driver's recommendation rationale";
222
+ rationaleLabel.textContent = "Why this runtime";
197
223
  var rationaleText = document.createElement("p");
198
224
  rationaleText.textContent = msg.recommendationRationale;
199
225
  rationale.appendChild(rationaleLabel);
200
226
  rationale.appendChild(rationaleText);
201
- card.appendChild(rationale);
227
+ brief.appendChild(rationale);
202
228
  }
203
229
 
204
230
  var config = document.createElement("div");
205
231
  config.className = "worker-proposal-config";
206
232
  var vendorField = document.createElement("label");
207
233
  vendorField.className = "worker-proposal-field worker-proposal-vendor-field";
208
- vendorField.innerHTML = '<span>Worker</span><span class="worker-proposal-vendor-control"><img alt=""></span>';
234
+ vendorField.innerHTML = '<span>Provider</span><span class="worker-proposal-vendor-control"><img alt=""></span>';
209
235
  var vendorSelect = buildVendorSelect(msg, runtimeSelection.vendor);
210
236
  vendorField.querySelector(".worker-proposal-vendor-control").appendChild(vendorSelect);
211
237
  config.appendChild(vendorField);
@@ -218,9 +244,15 @@ export function renderWorkerProposal(msg) {
218
244
  config.appendChild(modelField);
219
245
  var effortField = document.createElement("div");
220
246
  effortField.className = "worker-proposal-field worker-proposal-effort";
221
- effortField.innerHTML = '<span>Reasoning</span><div class="worker-proposal-effort-buttons"></div>';
247
+ effortField.innerHTML = '<span>Reasoning</span><div class="worker-proposal-effort-buttons" role="group" aria-label="Reasoning effort"></div>';
222
248
  config.appendChild(effortField);
223
249
  card.appendChild(config);
250
+ var runtime = document.createElement("p");
251
+ runtime.className = "worker-proposal-runtime hidden";
252
+ card.appendChild(runtime);
253
+ var refreshVendor = enhanceWorkerRuntimePicker(vendorSelect, "Provider");
254
+ var refreshModel = enhanceWorkerRuntimePicker(modelSelect, "Model");
255
+ card._refreshPickers = function () { refreshVendor(); refreshModel(); };
224
256
 
225
257
  var decision = document.createElement("div");
226
258
  decision.className = "worker-proposal-decision";
@@ -232,6 +264,7 @@ export function renderWorkerProposal(msg) {
232
264
  avatar.src = VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude;
233
265
  fillModels(modelSelect, vendor, msg, preferredModel);
234
266
  fillEffort(card, vendor, modelSelect.value, msg, preferredEffort);
267
+ card._refreshPickers();
235
268
  }
236
269
  card._syncWorkerProposalSelection = function (selection) {
237
270
  if (selection.vendor && vendorSelect.value !== selection.vendor) vendorSelect.value = selection.vendor;
@@ -246,6 +279,7 @@ export function renderWorkerProposal(msg) {
246
279
 
247
280
  var error = document.createElement("div");
248
281
  error.className = "worker-proposal-error hidden";
282
+ error.setAttribute("role", "alert");
249
283
  card.appendChild(error);
250
284
 
251
285
  var actions = document.createElement("div");
@@ -0,0 +1,115 @@
1
+ import { iconHtml, refreshIcons } from './icons.js';
2
+
3
+ // Keep the existing select as the value source; desktop uses a compact listbox.
4
+ export function enhanceWorkerRuntimePicker(select, label, popupHost) {
5
+ var wrapper = document.createElement('div');
6
+ wrapper.className = 'worker-runtime-picker';
7
+ select.parentNode.insertBefore(wrapper, select);
8
+ wrapper.appendChild(select);
9
+ var trigger = document.createElement('button');
10
+ trigger.type = 'button';
11
+ if (select.id) trigger.id = select.id + '-trigger';
12
+ trigger.className = 'worker-runtime-trigger';
13
+ trigger.setAttribute('aria-haspopup', 'listbox');
14
+ trigger.setAttribute('aria-expanded', 'false');
15
+ wrapper.appendChild(trigger);
16
+ var popup = null;
17
+ var observer = null;
18
+ function close(restore) {
19
+ if (!popup) return;
20
+ if (observer) { observer.disconnect(); observer = null; }
21
+ popup.remove();
22
+ popup = null;
23
+ trigger.setAttribute('aria-expanded', 'false');
24
+ document.removeEventListener('pointerdown', outside, true);
25
+ window.removeEventListener('resize', dismiss);
26
+ document.removeEventListener('scroll', scroll, true);
27
+ if (restore && trigger.isConnected && !trigger.disabled) trigger.focus();
28
+ }
29
+ function outside(event) { if (!wrapper.contains(event.target) && popup && !popup.contains(event.target)) close(false); }
30
+ function dismiss() { close(false); }
31
+ function scroll(event) { if (popup && !popup.contains(event.target)) close(false); }
32
+ function sync() {
33
+ close(false);
34
+ trigger.disabled = select.disabled;
35
+ var option = select.options[select.selectedIndex];
36
+ var text = option ? option.textContent : 'Automatic';
37
+ trigger.replaceChildren();
38
+ var value = document.createElement('span');
39
+ value.textContent = text;
40
+ trigger.appendChild(value);
41
+ trigger.insertAdjacentHTML('beforeend', iconHtml('chevron-down'));
42
+ trigger.setAttribute('aria-label', label + ': ' + text);
43
+ refreshIcons();
44
+ }
45
+ function open() {
46
+ if (trigger.disabled) return;
47
+ if (popup) { close(false); return; }
48
+ popup = document.createElement('div');
49
+ popup.className = 'worker-runtime-options';
50
+ popup.setAttribute('role', 'listbox');
51
+ popup.setAttribute('aria-label', label);
52
+ Array.from(select.options).forEach(function (option) {
53
+ var button = document.createElement('button');
54
+ button.type = 'button';
55
+ button.setAttribute('role', 'option');
56
+ button.setAttribute('aria-selected', String(option.selected));
57
+ button.disabled = option.disabled;
58
+ button.tabIndex = option.selected ? 0 : -1;
59
+ button.textContent = option.textContent;
60
+ button.addEventListener('click', function () {
61
+ select.value = option.value;
62
+ close(true);
63
+ select.dispatchEvent(new Event('change', {bubbles: true}));
64
+ sync();
65
+ });
66
+ popup.appendChild(button);
67
+ });
68
+ popup.addEventListener('keydown', function (event) {
69
+ if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); close(true); return; }
70
+ if (event.key === 'Tab') {
71
+ event.preventDefault();
72
+ var targets = Array.from((popupHost || document).querySelectorAll('button:not(:disabled), select:not(:disabled), input:not(:disabled), summary, a[href], [tabindex="0"]')).filter(function (el) {
73
+ return !popup.contains(el) && el.getClientRects().length && el.tabIndex >= 0;
74
+ });
75
+ var targetIndex = targets.indexOf(trigger) + (event.shiftKey ? -1 : 1);
76
+ var target = targets[popupHost ? (targetIndex + targets.length) % targets.length : targetIndex];
77
+ close(false);
78
+ if (target) target.focus();
79
+ else trigger.focus();
80
+ return;
81
+ }
82
+ var choices = Array.from(popup.querySelectorAll('button:not(:disabled)'));
83
+ var index = choices.indexOf(document.activeElement);
84
+ var next = index;
85
+ if (event.key === 'ArrowDown') next = (index + 1) % choices.length;
86
+ else if (event.key === 'ArrowUp') next = (index + choices.length - 1) % choices.length;
87
+ else if (event.key === 'Home') next = 0;
88
+ else if (event.key === 'End') next = choices.length - 1;
89
+ else if (event.key.length === 1) next = choices.findIndex(function (button, i) { return i > index && button.textContent.toLowerCase().startsWith(event.key.toLowerCase()); });
90
+ else return;
91
+ if (next >= 0 && choices[next]) { event.preventDefault(); choices.forEach(function (button) { button.tabIndex = -1; }); choices[next].tabIndex = 0; choices[next].focus(); }
92
+ });
93
+ (popupHost || document.body).appendChild(popup);
94
+ observer = new MutationObserver(function () { if (!trigger.isConnected) close(false); });
95
+ observer.observe(document.body, {childList: true, subtree: true});
96
+ var rect = trigger.getBoundingClientRect();
97
+ popup.style.width = Math.min(Math.max(rect.width, 180), window.innerWidth - 16) + 'px';
98
+ popup.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - popup.offsetWidth - 8)) + 'px';
99
+ var height = Math.min(popup.scrollHeight, 240);
100
+ popup.style.top = (rect.bottom + height + 12 <= innerHeight ? rect.bottom + 4 : Math.max(8, rect.top - height - 4)) + 'px';
101
+ trigger.setAttribute('aria-expanded', 'true');
102
+ var selected = popup.querySelector('[aria-selected="true"]:not(:disabled)') || popup.querySelector('button:not(:disabled)');
103
+ if (selected) { selected.tabIndex = 0; selected.focus(); }
104
+ document.addEventListener('pointerdown', outside, true);
105
+ window.addEventListener('resize', dismiss);
106
+ document.addEventListener('scroll', scroll, true);
107
+ }
108
+ trigger.addEventListener('click', open);
109
+ trigger.addEventListener('keydown', function (event) {
110
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { event.preventDefault(); open(); }
111
+ });
112
+ select.addEventListener('change', sync);
113
+ sync();
114
+ return sync;
115
+ }
@@ -0,0 +1,121 @@
1
+ // Temporary Clay announcements with an expandable saved-change history.
2
+ import { store } from './store.js';
3
+ import { sendYou, openYou } from './you.js';
4
+ import { relativeTime } from './project-logs-render.js';
5
+
6
+ function state() { return store.get('youActivity') || {}; }
7
+ function put(values) { store.set({ youActivity: Object.assign({}, state(), values) }); }
8
+ function el(id) { return document.getElementById(id); }
9
+ function describe(item) {
10
+ if (item.action === 'archive') return 'Clay stopped using a memory about you.';
11
+ return (item.action === 'update' ? 'Clay updated: ' : 'Clay remembered: ') + (item.summary || item.title);
12
+ }
13
+ function request(more) {
14
+ put({ requestId: sendYou('you_activity', { offset: more ? state().nextOffset : 0, limit: 20 }), append: !!more });
15
+ }
16
+ function idle() {
17
+ clearTimeout(state().typingTimer); clearTimeout(state().hideTimer);
18
+ put({ active: false, typingTimer: null, hideTimer: null });
19
+ el('you-activity').classList.remove('announcing');
20
+ }
21
+ function announce(message) {
22
+ if (state().active) { put({ queue: (state().queue || []).concat(message) }); return; }
23
+ put({ active: true });
24
+ el('you-activity').classList.add('announcing');
25
+ el('you-activity-toggle').setAttribute('aria-label', message);
26
+ el('you-activity-toggle').title = message;
27
+ var characters = Array.from(message);
28
+ var index = 0;
29
+ var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
30
+ function type() {
31
+ index = reduced ? characters.length : Math.min(characters.length, index + 2);
32
+ el('you-activity-text').textContent = characters.slice(0, index).join('');
33
+ el('you-activity-text').scrollLeft = el('you-activity-text').scrollWidth;
34
+ if (index < characters.length) put({ typingTimer: setTimeout(type, 30) });
35
+ else { el('you-announcement-status').textContent = message; put({ hideTimer: setTimeout(settle, 6500) }); }
36
+ }
37
+ type();
38
+ }
39
+ function settle() {
40
+ if (state().open || el('you-activity').matches(':hover, :focus-within')) {
41
+ put({ hideTimer: setTimeout(settle, 1000) }); return;
42
+ }
43
+ var queue = (state().queue || []).slice();
44
+ var next = queue.shift();
45
+ idle(); put({ queue: queue });
46
+ if (next) announce(next);
47
+ }
48
+ function close() {
49
+ put({ open: false });
50
+ el('you-activity-history').classList.add('hidden');
51
+ el('you-activity-toggle').setAttribute('aria-expanded', 'false');
52
+ }
53
+ function render() {
54
+ var items = state().items || [];
55
+ var list = el('you-activity-list');
56
+ list.replaceChildren();
57
+ if (!items.length) {
58
+ var empty = document.createElement('p');
59
+ empty.className = 'you-activity-empty';
60
+ empty.textContent = 'When Clay records something about you, it appears here.';
61
+ list.appendChild(empty);
62
+ }
63
+ items.forEach(function (item) {
64
+ var button = document.createElement('button');
65
+ button.type = 'button';
66
+ button.className = 'you-activity-entry';
67
+ var text = document.createElement('span'); text.textContent = describe(item);
68
+ var time = document.createElement('small'); time.textContent = relativeTime(item.at);
69
+ button.append(text, time);
70
+ button.addEventListener('click', function () { close(); openYou(item.action === 'archive' ? null : item.ref); });
71
+ list.appendChild(button);
72
+ });
73
+ el('you-activity-more').classList.toggle('hidden', state().nextOffset == null);
74
+ }
75
+ export function handleYouActivity(msg) {
76
+ if (!el('you-activity') || (msg.activity && msg.activity.seen)) return;
77
+ if (msg.type === 'you_reviewed') { put({ error: '' }); render(); return; }
78
+ if (msg.type === 'you_changed') {
79
+ if (msg.activity && msg.activity.error) { put({ error: msg.activity.error }); announce(msg.activity.error); return; }
80
+ put({ error: '' });
81
+ if (msg.activity && msg.activity.id && (state().seen || []).indexOf(msg.activity.id) === -1) {
82
+ put({ seen: (state().seen || []).concat(msg.activity.id).slice(-100) });
83
+ announce(describe(msg.activity));
84
+ }
85
+ request();
86
+ } else if (msg.operation === 'you_activity' && msg.requestId === state().requestId) {
87
+ if (!msg.ok) { put({ requestId: null, error: msg.error || 'Could not load Clay’s memory activity.' }); render(); return; }
88
+ var items = state().append ? (state().items || []).concat(msg.result.items) : msg.result.items;
89
+ put({ requestId: null, items: items, nextOffset: msg.result.nextOffset, error: '' });
90
+ render();
91
+ }
92
+ }
93
+ export function initYouActivity() {
94
+ var host = document.createElement('div'); host.id = 'you-activity';
95
+ host.innerHTML = '<button id="you-activity-toggle" type="button" aria-expanded="false" aria-controls="you-activity-history"><span id="you-activity-text" aria-hidden="true"></span></button>' +
96
+ '<section id="you-activity-history" class="hidden" aria-label="Clay’s memory activity"><header><strong>Clay’s memory</strong><button id="you-activity-close" type="button" aria-label="Close memory activity">Close</button></header>' +
97
+ '<div id="you-activity-list"></div><button id="you-activity-more" class="hidden" type="button">Load earlier activity</button><button id="you-activity-open" type="button">Open Me</button></section>';
98
+ document.querySelector('.top-bar-left-pills').prepend(host);
99
+ var live = document.createElement('span'); live.id = 'you-announcement-status'; live.setAttribute('role', 'status'); live.setAttribute('aria-live', 'polite'); document.body.appendChild(live);
100
+ el('you-activity-toggle').addEventListener('click', function () {
101
+ if (state().open) { close(); return; }
102
+ put({ open: true });
103
+ el('you-activity-history').classList.remove('hidden');
104
+ this.setAttribute('aria-expanded', 'true');
105
+ request();
106
+ });
107
+ el('you-activity-close').addEventListener('click', function () { close(); el('you-activity-toggle').focus(); });
108
+ el('you-activity-open').addEventListener('click', function () { close(); openYou(); });
109
+ el('you-activity-more').addEventListener('click', function () { request(true); });
110
+ document.addEventListener('click', function (event) { if (state().open && !host.contains(event.target)) close(); });
111
+ host.addEventListener('keydown', function (event) {
112
+ if (event.key === 'Escape') { event.stopPropagation(); close(); el('you-activity-toggle').focus(); }
113
+ });
114
+ store.subscribe(function (next, previous) {
115
+ if (next.myUserId !== previous.myUserId) { idle(); store.set({ youActivity: {} }); close(); render(); }
116
+ if (next.currentSlug !== previous.currentSlug) close();
117
+ if ((!previous.connected && next.connected) || (next.myUserId !== previous.myUserId && next.connected)) request();
118
+ });
119
+ render();
120
+ if (store.get('connected')) request();
121
+ }
@@ -0,0 +1,251 @@
1
+ // The owner's You workbench reuses the Logs reading surfaces.
2
+ import { store } from './store.js';
3
+ import { getWs } from './ws-ref.js';
4
+ import { refreshIcons } from './icons.js';
5
+ import { renderList, renderDetail, categoryLabel } from './project-logs-render.js';
6
+ import { registerRightWorkbench, claimRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
7
+
8
+ function state() { return store.get('you') || {}; }
9
+ function put(values) { store.set({ you: Object.assign({}, state(), values) }); }
10
+ function element(id) { return document.getElementById(id); }
11
+
12
+ export function sendYou(type, values) {
13
+ var ws = getWs();
14
+ if (!ws || ws.readyState !== 1) return null;
15
+ var sequence = (store.get('youSequence') || 0) + 1;
16
+ store.set({ youSequence: sequence });
17
+ var requestId = 'you-' + sequence;
18
+ ws.send(JSON.stringify(Object.assign({}, values || {}, { type: type, requestId: requestId })));
19
+ return requestId;
20
+ }
21
+
22
+ function requestAttention() { put({ attentionId: sendYou('you_attention') }); }
23
+ function showAttention(result) {
24
+ if (result.version < (state().attentionVersion || 0)) return;
25
+ put({ unread: result.unread, attentionVersion: result.version });
26
+ var badge = element('me-unread-badge');
27
+ badge.hidden = !result.unread;
28
+ badge.textContent = result.unread > 99 ? '99+' : String(result.unread);
29
+ var label = 'Me — what Clay knows about me' + (result.unread ? ', ' + result.unread + (result.unread === 1 ? ' unread memory' : ' unread memories') : '');
30
+ element('you-button').setAttribute('aria-label', label);
31
+ element('you-button').title = label;
32
+ }
33
+ function acknowledgeVisible() {
34
+ var s = state();
35
+ if (!s.open || document.visibilityState !== 'visible' || s.selected !== s.renderedRef || !s.renderedRevision || s.seenId) return;
36
+ var key = s.renderedRef + ':' + s.renderedRevision;
37
+ if (s.acknowledged === key) return;
38
+ put({ seenId: sendYou('you_seen', { ref: s.renderedRef, revision: s.renderedRevision }), acknowledging: key });
39
+ }
40
+ function unreadRows(entries) {
41
+ element('you-list').querySelectorAll('.project-log-row').forEach(function (row) {
42
+ var entry = entries.find(function (item) { return item.ref === row.dataset.ref; });
43
+ if (!entry || !entry.unread) return;
44
+ var badge = document.createElement('span');
45
+ badge.className = 'me-new-badge'; badge.textContent = entry.revisions > 1 ? 'Updated' : 'New';
46
+ row.querySelector('.project-log-row-head').appendChild(badge);
47
+ });
48
+ }
49
+
50
+ function status(text) { var el = element('you-status'); if (el) el.textContent = text; }
51
+ function showList() {
52
+ var wasReading = !!state().selected;
53
+ put({ selected: null, readId: null });
54
+ element('you-detail').classList.add('hidden');
55
+ element('you-list').classList.remove('hidden');
56
+ element('you-toolbar').classList.remove('hidden');
57
+ element('me-introduction').classList.remove('hidden');
58
+ element('you-back').classList.add('hidden');
59
+ element('you-more').classList.toggle('hidden', state().nextOffset == null);
60
+ element('you-list').scrollTop = state().scroll || 0;
61
+ if (wasReading && state().open) requestList();
62
+ }
63
+ function requestList(more) {
64
+ var s = state();
65
+ var requestId = sendYou('you_list', { query: s.query || '', category: s.category || '', offset: more ? s.nextOffset : 0 });
66
+ put({ listId: requestId, append: !!more });
67
+ status(requestId ? 'Loading…' : 'Reconnect to load what Clay knows about you.');
68
+ }
69
+ function requestEntry(ref) {
70
+ put({ readId: sendYou('you_read', { ref: ref }), selected: ref, scroll: element('you-list').scrollTop });
71
+ status(state().readId ? 'Loading…' : 'Reconnect to read this memory.');
72
+ }
73
+ function windowState(wide, fullscreen) {
74
+ put({ wide: wide, fullscreen: fullscreen });
75
+ element('you-panel').classList.toggle('project-logs-wide', wide && !fullscreen);
76
+ element('you-panel').classList.toggle('panel-fullscreen', fullscreen);
77
+ element('you-wide').setAttribute('aria-pressed', String(!!wide));
78
+ element('you-fullscreen').setAttribute('aria-pressed', String(!!fullscreen));
79
+ element('you-wide').disabled = !!fullscreen;
80
+ }
81
+ function filter(categories) {
82
+ var host = element('you-categories');
83
+ host.replaceChildren();
84
+ var summary = document.createElement('summary');
85
+ summary.className = 'project-logs-filter';
86
+ summary.textContent = state().category ? categoryLabel(state().category) : 'All categories';
87
+ host.appendChild(summary);
88
+ var menu = document.createElement('div');
89
+ menu.className = 'you-category-options';
90
+ [''].concat(categories).forEach(function (category) {
91
+ var button = document.createElement('button');
92
+ button.type = 'button';
93
+ button.textContent = category ? categoryLabel(category) : 'All categories';
94
+ button.setAttribute('aria-pressed', String(category === (state().category || '')));
95
+ button.addEventListener('click', function () { put({ category: category }); host.open = false; requestList(); });
96
+ menu.appendChild(button);
97
+ });
98
+ host.appendChild(menu);
99
+ }
100
+ function ensurePanel() {
101
+ if (element('you-panel')) return;
102
+ var panel = document.createElement('section');
103
+ panel.id = 'you-panel';
104
+ panel.className = 'project-logs-panel hidden';
105
+ panel.setAttribute('aria-label', 'Me — what Clay knows about me');
106
+ panel.innerHTML = '<header class="project-logs-topbar">' +
107
+ '<button id="you-back" class="project-logs-icon-btn hidden" aria-label="Back to memories"><i data-lucide="arrow-left"></i></button>' +
108
+ '<span class="project-logs-title"><span class="me-mark" aria-hidden="true"><i data-lucide="user-round"></i></span>Me</span>' +
109
+ '<span class="project-logs-subtitle">What Clay knows about me</span>' +
110
+ '<div class="project-logs-window-actions"><button id="you-wide" class="project-logs-icon-btn" aria-label="Widen Me" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button>' +
111
+ '<button id="you-fullscreen" class="project-logs-icon-btn" aria-label="Toggle Me fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button>' +
112
+ '<button id="you-close" class="project-logs-icon-btn" aria-label="Close Me"><i data-lucide="x"></i></button></div></header>' +
113
+ '<p id="me-introduction" class="me-introduction">Clay remembers your preferences, working style, and what matters to you, so you don’t have to explain them again in every project or to every Mate. Here you can review what Clay knows about you and leave comments to refine it.</p>' +
114
+ '<div id="you-toolbar" class="project-logs-toolbar"><label class="project-logs-search"><i data-lucide="search"></i><input id="you-search" type="search" aria-label="Search memories" placeholder="Search what Clay knows" autocomplete="off"></label><details id="you-categories" class="you-categories"></details></div>' +
115
+ '<p id="you-status" class="you-status" role="status"></p>' +
116
+ '<div id="you-pending" class="you-pending hidden"><span></span><button id="you-retry" type="button">Ask Clay to review</button></div>' +
117
+ '<div id="you-list" class="project-logs-list" role="list"></div><main id="you-detail" class="project-logs-detail hidden"></main>' +
118
+ '<button id="you-more" type="button" class="you-more hidden">Load more</button>';
119
+ document.getElementById('main-panels').appendChild(panel);
120
+ element('you-close').addEventListener('click', closeYou);
121
+ element('you-back').addEventListener('click', showList);
122
+ element('you-wide').addEventListener('click', function () { windowState(!state().wide, false); });
123
+ element('you-fullscreen').addEventListener('click', function () { windowState(state().wide, !state().fullscreen); });
124
+ element('you-more').addEventListener('click', function () { requestList(true); });
125
+ element('you-search').addEventListener('input', function () {
126
+ clearTimeout(state().searchTimer);
127
+ put({ query: this.value, searchTimer: setTimeout(function () { requestList(); }, 180) });
128
+ });
129
+ element('you-retry').addEventListener('click', function () {
130
+ put({ retryId: sendYou('you_retry') });
131
+ status(state().retryId ? 'Asked Clay to review pending observations.' : 'Reconnect to request a review.');
132
+ });
133
+ panel.addEventListener('keydown', function (event) {
134
+ if (event.key === 'Escape') { event.stopPropagation(); closeYou(); }
135
+ if (event.key === 'Tab' && window.matchMedia('(max-width: 1023px)').matches) {
136
+ var nodes = Array.from(panel.querySelectorAll('button:not(:disabled),input,textarea,summary')).filter(function (node) { return node.getClientRects().length; });
137
+ var first = nodes[0]; var last = nodes[nodes.length - 1];
138
+ if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
139
+ else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
140
+ }
141
+ });
142
+ filter([]);
143
+ refreshIcons();
144
+ }
145
+
146
+ export function openYou(ref) {
147
+ ensurePanel();
148
+ put({ returnFocus: document.activeElement, open: true });
149
+ claimRightWorkbench('you');
150
+ element('you-button').classList.add('active');
151
+ element('you-button').setAttribute('aria-pressed', 'true');
152
+ element('you-panel').classList.remove('hidden');
153
+ element('you-panel').setAttribute('role', 'dialog');
154
+ element('you-panel').setAttribute('aria-modal', String(window.matchMedia('(max-width: 1023px)').matches));
155
+ windowState(state().wide, false);
156
+ showList();
157
+ requestList();
158
+ if (ref) requestEntry(ref);
159
+ if (ref) element('you-close').focus(); else element('you-search').focus();
160
+ }
161
+ export function closeYou() {
162
+ releaseRightWorkbench('you');
163
+ if (!state().open) return;
164
+ element('you-panel').classList.add('hidden');
165
+ element('you-button').classList.remove('active');
166
+ element('you-button').setAttribute('aria-pressed', 'false');
167
+ windowState(state().wide, false);
168
+ var focus = state().returnFocus;
169
+ put({ open: false });
170
+ if (focus && focus.isConnected) focus.focus();
171
+ }
172
+ export function handleYouMessage(msg) {
173
+ if (msg.type === 'you_reviewed') { if (state().open) { requestList(); if (state().selected) requestEntry(state().selected); } return; }
174
+ if (msg.type === 'you_changed') {
175
+ requestAttention();
176
+ if (msg.activity && msg.activity.seen) { if (state().open && !state().selected) requestList(); return; }
177
+ if (msg.activity && msg.activity.error) { status(msg.activity.error); return; }
178
+ if (state().open) { requestList(); if (state().selected) requestEntry(state().selected); }
179
+ return;
180
+ }
181
+ var s = state();
182
+ var key = { you_attention: 'attentionId', you_seen: 'seenId', you_list: 'listId', you_read: 'readId', you_comment: 'commentId', you_retry: 'retryId' }[msg.operation];
183
+ if (!key || !s[key] || s[key] !== msg.requestId) return;
184
+ var cleared = {}; cleared[key] = null; put(cleared);
185
+ if (msg.operation === 'you_comment' && element('you-comment-input')) element('you-comment-input').form.querySelector('button').disabled = false;
186
+ if (msg.operation === 'you_attention') { if (msg.ok) showAttention(msg.result); return; }
187
+ if (msg.operation === 'you_seen') {
188
+ if (msg.ok) { put({ acknowledged: s.acknowledging }); showAttention(msg.result); acknowledgeVisible(); }
189
+ return;
190
+ }
191
+ if (!state().open && msg.operation === 'you_read') return;
192
+ if (!msg.ok) { if (msg.operation === 'you_read') showList(); status(msg.error || 'Clay could not complete this request.'); return; }
193
+ status('');
194
+ var result = msg.result;
195
+ if (msg.operation === 'you_list') {
196
+ var entries = s.append ? (s.entries || []).concat(result.items) : result.items;
197
+ put({ entries: entries, nextOffset: result.nextOffset });
198
+ var emptyMessage = state().query || state().category
199
+ ? 'No memories match this search. Try another keyword or category.'
200
+ : 'No memories yet. They’ll appear here as Clay gets to know you.';
201
+ renderList(element('you-list'), entries, requestEntry, emptyMessage);
202
+ unreadRows(entries);
203
+ filter(result.categories);
204
+ element('you-more').classList.toggle('hidden', result.nextOffset == null || !!state().selected);
205
+ element('you-pending').classList.toggle('hidden', !result.pending);
206
+ element('you-pending').querySelector('span').textContent = result.pending + (result.pending === 1 ? ' observation awaiting Clay’s review' : ' observations awaiting Clay’s review');
207
+ } else if (msg.operation === 'you_read') {
208
+ element('me-introduction').classList.add('hidden');
209
+ element('you-list').classList.add('hidden'); element('you-toolbar').classList.add('hidden'); element('you-more').classList.add('hidden');
210
+ element('you-detail').classList.remove('hidden'); element('you-back').classList.remove('hidden');
211
+ var previousInput = element('you-comment-input');
212
+ var draft = previousInput && previousInput.dataset.ref === result.ref ? previousInput.value : '';
213
+ renderDetail(element('you-detail'), result, { commentPrefix: 'you', commentReviewer: 'Clay', onComment: function (ref, body, feedback, input) {
214
+ if (state().commentId) return;
215
+ put({ commentId: sendYou('you_comment', { ref: ref, body: body }), commentRef: ref });
216
+ input.form.querySelector('button').disabled = !!state().commentId;
217
+ feedback.textContent = state().commentId ? 'Sending to Clay for review…' : 'Reconnect to post your comment.';
218
+ } });
219
+ element('you-comment-input').value = draft;
220
+ element('you-comment-input').dataset.ref = result.ref;
221
+ element('you-comment-input').form.querySelector('button').disabled = !!state().commentId;
222
+ var evidence = document.createElement('p'); evidence.className = 'you-evidence';
223
+ evidence.textContent = (result.evidenceType === 'inferred' ? 'Inferred from: ' : 'You said: ') + (result.evidence || '') + '\nClay’s decision: ' + result.reason;
224
+ element('you-detail').querySelector('.project-log-markdown').after(evidence);
225
+ put({ renderedRef: result.ref, renderedRevision: result.revisions });
226
+ requestAnimationFrame(acknowledgeVisible);
227
+ } else if (msg.operation === 'you_comment') {
228
+ if (element('you-comment-input') && element('you-comment-input').dataset.ref === s.commentRef) element('you-comment-input').value = '';
229
+ if (state().selected) requestEntry(state().selected);
230
+ }
231
+ }
232
+ export function initYou() {
233
+ registerRightWorkbench('you', closeYou);
234
+ document.addEventListener('visibilitychange', acknowledgeVisible);
235
+ if (store.get('connected')) requestAttention();
236
+ element('you-button').addEventListener('click', function () { openYou(); });
237
+ store.subscribe(function (next, previous) {
238
+ if (next.currentSlug !== previous.currentSlug || next.myUserId !== previous.myUserId) {
239
+ closeYou(); clearTimeout(state().searchTimer);
240
+ if (next.myUserId !== previous.myUserId) { if (element('you-panel')) element('you-panel').remove(); put({ entries: [], query: '', category: '', attentionVersion: 0, acknowledged: null, attentionId: null, seenId: null }); showAttention({ unread: 0, version: 0 }); }
241
+ put({ listId: null, readId: null, commentId: null, retryId: null, seenId: null });
242
+ if (element('you-comment-input')) element('you-comment-input').form.querySelector('button').disabled = false;
243
+ }
244
+ if (previous.connected && !next.connected) {
245
+ put({ commentId: null, seenId: null, attentionId: null });
246
+ if (element('you-comment-input')) element('you-comment-input').form.querySelector('button').disabled = false;
247
+ if (state().open) status('Disconnected. Reconnect before sending or refreshing.');
248
+ }
249
+ if ((!previous.connected && next.connected) || (next.myUserId !== previous.myUserId && next.connected)) { requestAttention(); if (state().open) { requestList(); acknowledgeVisible(); } }
250
+ });
251
+ }