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.
- package/LICENSES/README.md +4 -0
- package/LICENSES/dicebear-avatars.md +16 -0
- package/lib/daemon-project-access.js +7 -1
- package/lib/daemon.js +2 -0
- package/lib/dicebear-avatar.js +62 -0
- package/lib/knowledge-db-service.js +175 -0
- package/lib/knowledge-db-validation.js +0 -0
- package/lib/knowledge-document-index.js +49 -0
- package/lib/knowledge-draft-store.js +56 -0
- package/lib/knowledge-file-store.js +199 -0
- package/lib/knowledge-markdown.js +128 -0
- package/lib/mate-knowledge-mcp-server.js +29 -6
- package/lib/mate-knowledge-service.js +23 -2
- package/lib/mate-knowledge-sync.js +36 -21
- package/lib/mate-workspace-color.js +4 -0
- package/lib/mates.js +18 -0
- package/lib/project-connection.js +36 -2
- package/lib/project-debate-proposal.js +6 -0
- package/lib/project-debate-workspace.js +43 -0
- package/lib/project-debate.js +1 -0
- package/lib/project-file-path.js +2 -3
- package/lib/project-knowledge.js +315 -136
- package/lib/project-mate-creation-proposal.js +1 -0
- package/lib/project-request-access.js +13 -1
- package/lib/project-sessions.js +39 -2
- package/lib/project-user-knowledge.js +60 -0
- package/lib/project.js +59 -18
- package/lib/public/app.js +73 -34
- package/lib/public/clay-studio-wordmark-bold.svg +20 -0
- package/lib/public/css/clay-workspace.css +105 -0
- package/lib/public/css/debates-workbench.css +47 -0
- package/lib/public/css/filebrowser.css +10 -0
- package/lib/public/css/home-debate-live.css +26 -0
- package/lib/public/css/home-hub.css +3 -1
- package/lib/public/css/home-mate-model-picker.css +15 -131
- package/lib/public/css/home-mate-settings.css +168 -16
- package/lib/public/css/icon-strip.css +249 -32
- package/lib/public/css/mate-conversation.css +176 -0
- package/lib/public/css/mate-knowledge-workbench.css +118 -0
- package/lib/public/css/mate-workspace-color.css +16 -0
- package/lib/public/css/mates.css +28 -28
- package/lib/public/css/messages.css +1 -114
- package/lib/public/css/mobile-nav.css +16 -16
- package/lib/public/css/overlays.css +8 -0
- package/lib/public/css/project-logs.css +7 -0
- package/lib/public/css/project-mate-creation.css +105 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/session-folders.css +80 -21
- package/lib/public/css/sidebar.css +183 -107
- package/lib/public/css/title-bar.css +151 -0
- package/lib/public/css/worker-delegation.css +47 -0
- package/lib/public/css/worker-proposal.css +72 -150
- package/lib/public/css/you.css +74 -0
- package/lib/public/index.html +33 -101
- package/lib/public/modules/account-island.js +230 -0
- package/lib/public/modules/app-cursors.js +19 -43
- package/lib/public/modules/app-debate-ui.js +4 -3
- package/lib/public/modules/app-header.js +106 -0
- package/lib/public/modules/app-message-router.js +12 -3
- package/lib/public/modules/app-messages.js +34 -9
- package/lib/public/modules/app-notifications.js +4 -7
- package/lib/public/modules/app-projects.js +5 -38
- package/lib/public/modules/app-rendering.js +12 -13
- package/lib/public/modules/avatar.js +25 -9
- package/lib/public/modules/chat-render-runtime.js +16 -4
- package/lib/public/modules/command-palette.js +2 -15
- package/lib/public/modules/cursor-sharing-preference.js +3 -3
- package/lib/public/modules/debate-controls.js +299 -0
- package/lib/public/modules/debate.js +2 -0
- package/lib/public/modules/debates-workbench.js +339 -0
- package/lib/public/modules/default-ai.js +81 -65
- package/lib/public/modules/filebrowser.js +28 -8
- package/lib/public/modules/home-debate-controls.js +18 -289
- package/lib/public/modules/home-mate-avatar-editor.js +228 -0
- package/lib/public/modules/home-mate-color-picker.js +66 -0
- package/lib/public/modules/home-mate-creation.js +1 -1
- package/lib/public/modules/home-mate-instructions.js +90 -0
- package/lib/public/modules/home-mate-model-picker.js +80 -271
- package/lib/public/modules/home-mate-profile-editor.js +72 -0
- package/lib/public/modules/home-mate-settings.js +118 -125
- package/lib/public/modules/home-shell.js +3 -0
- package/lib/public/modules/home-surface-boot.js +1 -9
- package/lib/public/modules/html-preview.js +19 -0
- package/lib/public/modules/icon-strip-tooltip.js +95 -0
- package/lib/public/modules/knowledge-db-edit-state.js +35 -0
- package/lib/public/modules/knowledge-db-workbench.js +234 -0
- package/lib/public/modules/knowledge-history-state.js +55 -0
- package/lib/public/modules/knowledge-markdown-editor.js +57 -0
- package/lib/public/modules/knowledge-preview-links.js +70 -0
- package/lib/public/modules/knowledge-workbench-shell.js +21 -0
- package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
- package/lib/public/modules/mate-knowledge-workbench.js +500 -0
- package/lib/public/modules/mate-knowledge.js +11 -3
- package/lib/public/modules/mate-management.js +54 -10
- package/lib/public/modules/mate-sidebar.js +11 -1
- package/lib/public/modules/mate-workspace-color.js +33 -0
- package/lib/public/modules/mention.js +5 -13
- package/lib/public/modules/profile.js +51 -18
- package/lib/public/modules/project-chat-identity.js +79 -0
- package/lib/public/modules/project-debate-planning-workspace.js +250 -0
- package/lib/public/modules/project-debate-workspace.js +185 -0
- package/lib/public/modules/project-header-identity.js +127 -0
- package/lib/public/modules/project-logs-render.js +9 -4
- package/lib/public/modules/project-mate-creation-workspace.js +414 -0
- package/lib/public/modules/project-mate-navigation.js +238 -0
- package/lib/public/modules/project-session-navigation.js +29 -0
- package/lib/public/modules/project-switcher.js +6 -8
- package/lib/public/modules/projects-hub-view.js +5 -6
- package/lib/public/modules/projects-hub.js +2 -6
- package/lib/public/modules/qrcode.js +27 -8
- package/lib/public/modules/search-clay-chat.js +8 -11
- package/lib/public/modules/session-activity-tooltip.js +12 -0
- package/lib/public/modules/session-create-form.js +86 -14
- package/lib/public/modules/session-create.js +50 -7
- package/lib/public/modules/session-list-visibility.js +41 -0
- package/lib/public/modules/session-presence-ui.js +4 -1
- package/lib/public/modules/session-title-marquee.js +51 -0
- package/lib/public/modules/sidebar-mates.js +3 -50
- package/lib/public/modules/sidebar-mobile.js +63 -60
- package/lib/public/modules/sidebar-projects.js +84 -97
- package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
- package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
- package/lib/public/modules/sidebar-sessions.js +49 -50
- package/lib/public/modules/sidebar.js +11 -13
- package/lib/public/modules/thinking-lifecycle.js +7 -6
- package/lib/public/modules/tool-palette-order.js +4 -3
- package/lib/public/modules/tool-palette.js +13 -3
- package/lib/public/modules/tools.js +22 -22
- package/lib/public/modules/worker-proposal.js +42 -8
- package/lib/public/modules/worker-runtime-picker.js +115 -0
- package/lib/public/modules/you-activity.js +121 -0
- package/lib/public/modules/you.js +251 -0
- package/lib/public/style.css +16 -8
- package/lib/public/vendor/codemirror/LICENSE +21 -0
- package/lib/public/vendor/codemirror/codemirror.css +344 -0
- package/lib/public/vendor/codemirror/codemirror.js +9884 -0
- package/lib/public/vendor/codemirror/continuelist.js +101 -0
- package/lib/public/vendor/codemirror/markdown.js +886 -0
- package/lib/public/vendor/codemirror/show-hint.css +37 -0
- package/lib/public/vendor/codemirror/show-hint.js +523 -0
- package/lib/sdk-bridge.js +15 -3
- package/lib/server-admin.js +11 -0
- package/lib/server-home-chat-events.js +1 -1
- package/lib/server-home-chat.js +25 -3
- package/lib/server-home-debate-planning.js +4 -1
- package/lib/server-home-debates.js +2 -0
- package/lib/server-home-mate-creation.js +4 -1
- package/lib/server-mate-instructions.js +44 -0
- package/lib/server-mates.js +12 -3
- package/lib/server-palette.js +1 -1
- package/lib/server-settings.js +65 -1
- package/lib/server-user-knowledge.js +119 -0
- package/lib/server.js +21 -1
- package/lib/session-visibility.js +7 -2
- package/lib/sessions.js +32 -6
- package/lib/user-knowledge-service.js +167 -0
- package/lib/users-permissions.js +7 -0
- package/lib/users-project-mate-filter-preferences.js +39 -0
- package/lib/users.js +6 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +50 -0
- 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",
|
|
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">
|
|
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
|
-
|
|
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 = "
|
|
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
|
-
|
|
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>
|
|
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
|
+
}
|