clay-server 5.2.0-beta.9 → 5.2.1-beta.1
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/README.md +0 -4
- package/lib/clay-product-control-tools.js +1 -0
- package/lib/clay-product-control.js +14 -11
- package/lib/clay-vendor-usage.js +44 -0
- package/lib/github-star-count.js +147 -0
- package/lib/notes-checklist.js +43 -1
- package/lib/notes.js +2 -1
- package/lib/project-session-notes.js +33 -8
- package/lib/project-sessions.js +63 -24
- package/lib/project-start-protocol.js +29 -0
- package/lib/project-vendor-login.js +2 -0
- package/lib/project.js +5 -1
- package/lib/public/app.js +30 -18
- package/lib/public/css/command-palette.css +12 -2
- package/lib/public/css/contextual-clay.css +1 -1
- package/lib/public/css/filebrowser.css +4 -5
- package/lib/public/css/mate-loading-scene.css +21 -0
- package/lib/public/css/messages.css +30 -11
- package/lib/public/css/overlays.css +67 -256
- package/lib/public/css/project-mate-creation.css +5 -6
- package/lib/public/css/project-start.css +108 -0
- package/lib/public/css/projects-hub.css +53 -14
- package/lib/public/css/right-workbench.css +28 -0
- package/lib/public/css/settings-shell.css +103 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/title-bar.css +99 -0
- package/lib/public/index.html +54 -66
- package/lib/public/modules/account-transport-settings.js +8 -2
- package/lib/public/modules/admin-settings-email.js +7 -2
- package/lib/public/modules/admin-settings-invitations.js +2 -2
- package/lib/public/modules/admin-settings-projects.js +2 -2
- package/lib/public/modules/admin-settings-ui.js +20 -4
- package/lib/public/modules/admin-settings-users.js +4 -4
- package/lib/public/modules/app-home-hub.js +2 -1
- package/lib/public/modules/app-messages.js +10 -2
- package/lib/public/modules/app-projects.js +1 -407
- package/lib/public/modules/app-rate-limit.js +12 -1
- package/lib/public/modules/clay-tool-presentation.js +1 -0
- package/lib/public/modules/contextual-clay.js +3 -1
- package/lib/public/modules/github-star.js +66 -0
- package/lib/public/modules/home-surface-boot.js +0 -1
- package/lib/public/modules/mate-loading-scene.js +17 -0
- package/lib/public/modules/pair-result-status.js +89 -39
- package/lib/public/modules/pane-appearance-bridge.js +59 -0
- package/lib/public/modules/profile.js +11 -3
- package/lib/public/modules/project-mate-creation-workspace.js +13 -4
- package/lib/public/modules/project-session-visibility-settings.js +19 -16
- package/lib/public/modules/project-settings-environment.js +12 -1
- package/lib/public/modules/project-settings.js +119 -129
- package/lib/public/modules/project-start-dialog.js +130 -0
- package/lib/public/modules/project-start-folder-state.js +78 -0
- package/lib/public/modules/project-start-folder-view.js +190 -0
- package/lib/public/modules/project-start-forms.js +66 -0
- package/lib/public/modules/project-start-path.js +60 -0
- package/lib/public/modules/project-start-state.js +54 -0
- package/lib/public/modules/project-start-submit.js +67 -0
- package/lib/public/modules/projects-hub-view.js +32 -44
- package/lib/public/modules/projects-hub.js +39 -28
- package/lib/public/modules/server-settings.js +36 -91
- package/lib/public/modules/settings-project-target.js +56 -0
- package/lib/public/modules/settings-select.js +1 -1
- package/lib/public/modules/settings-shell-registry.js +88 -0
- package/lib/public/modules/settings-shell-view.js +104 -0
- package/lib/public/modules/settings-shell.js +342 -0
- package/lib/public/modules/sidebar-mobile.js +8 -30
- package/lib/public/modules/sidebar-projects.js +2 -1
- package/lib/public/modules/theme.js +31 -3
- package/lib/public/modules/topbar-workbench-controls.js +102 -0
- package/lib/public/modules/user-settings-integrations.js +2 -1
- package/lib/public/modules/user-settings-layout.js +12 -34
- package/lib/public/modules/user-settings-profile.js +1 -1
- package/lib/public/modules/user-settings.js +18 -85
- package/lib/public/modules/vendor-usage-format.js +93 -0
- package/lib/public/modules/vendor-usage-list.js +45 -0
- package/lib/public/modules/vendor-usage-model.js +215 -0
- package/lib/public/modules/vendor-usage-overview.js +323 -0
- package/lib/public/style.css +13 -10
- package/lib/public/welcome-art/conversation-desk.png +0 -0
- package/lib/public/welcome-art/mate-clay.png +0 -0
- package/lib/public/welcome-art/project-clay.png +0 -0
- package/lib/sdk-bridge.js +6 -1
- package/lib/sdk-message-processor.js +9 -1
- package/lib/sdk-quota-snapshot.js +20 -2
- package/lib/server-github-stars.js +39 -0
- package/lib/server-global-ws.js +21 -17
- package/lib/server.js +11 -0
- package/lib/session-notes-mcp-server.js +26 -4
- package/lib/session-notes-writes.js +88 -0
- package/lib/session-visibility.js +20 -8
- package/lib/sessions.js +12 -2
- package/lib/vendor-usage-acp-readers.js +97 -0
- package/lib/vendor-usage-capabilities.js +88 -0
- package/lib/vendor-usage-http-readers.js +214 -0
- package/lib/vendor-usage-metrics.js +47 -0
- package/lib/vendor-usage-native-readers.js +106 -0
- package/lib/vendor-usage-overview.js +406 -0
- package/lib/vendor-usage-service.js +281 -0
- package/lib/ws-schema.js +4 -2
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex-query-contract.js +1 -0
- package/lib/yoke/adapters/codex.js +45 -12
- package/lib/yoke/claude-hook-installer.js +1 -0
- package/lib/yoke/codex-dynamic-tool-names.js +80 -0
- package/lib/yoke/codex-model-catalog.js +37 -3
- package/lib/yoke/muse-services.js +16 -3
- package/lib/yoke/providers/claude-model-descriptions.js +22 -2
- package/lib/yoke/vendor-registry.js +6 -2
- package/package.json +2 -1
- package/lib/public/modules/server-settings-navigation.js +0 -39
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
3
|
import { showConfirm } from './app-misc.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
4
5
|
|
|
5
6
|
var statusGeneration = 0;
|
|
7
|
+
// Whether the review tray is open lives in the store (pairResultTrayOpen), so it
|
|
8
|
+
// survives server re-renders and resets on clear or session switch.
|
|
9
|
+
function isTrayOpen() { return !!store.get("pairResultTrayOpen"); }
|
|
10
|
+
function setTrayOpen(open) { if (isTrayOpen() !== open) store.set({ pairResultTrayOpen: open }); }
|
|
6
11
|
|
|
7
12
|
function retrySource(item) {
|
|
8
13
|
return { item: item, generation: statusGeneration, projectSlug: store.get("currentSlug") || store.get("activeProjectSlug") || null,
|
|
@@ -43,56 +48,100 @@ function dismiss(items) {
|
|
|
43
48
|
});
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
function worstState(items) {
|
|
52
|
+
if (items.some(function (item) { return item.state === "blocked"; })) return "blocked";
|
|
53
|
+
if (items.some(function (item) { return item.state === "uncertain"; })) return "uncertain";
|
|
54
|
+
return "pending";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function stateWords(state) {
|
|
58
|
+
return state === "blocked" ? "blocked" : (state === "uncertain" ? "delivery uncertain" : "pending");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function button(className, label, icon) {
|
|
62
|
+
var node = document.createElement("button");
|
|
63
|
+
node.type = "button"; node.className = className;
|
|
64
|
+
if (label) node.setAttribute("aria-label", label);
|
|
65
|
+
if (icon) node.innerHTML = iconHtml(icon);
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renderCard(item) {
|
|
70
|
+
var card = document.createElement("div");
|
|
71
|
+
card.className = "pair-result-status-card pair-result-status-" + item.state;
|
|
72
|
+
var title = document.createElement("strong");
|
|
73
|
+
title.textContent = item.state === "blocked" ? "Split Worker result blocked" :
|
|
74
|
+
(item.state === "uncertain" ? (item.hasCompletedResult === false ? "Split Worker recovery uncertain" : "Split Worker result delivery uncertain") : "Split Worker result pending");
|
|
75
|
+
var message = document.createElement("span");
|
|
76
|
+
message.textContent = item.message || "The completed result is retained.";
|
|
77
|
+
card.appendChild(title); card.appendChild(message);
|
|
78
|
+
if (item.hasCompletedResult !== false) {
|
|
79
|
+
var details = document.createElement("details"), summary = document.createElement("summary"), body = document.createElement("pre");
|
|
80
|
+
summary.textContent = "View completed result";
|
|
81
|
+
body.textContent = item.error ? "Error: " + item.error : (item.response || "No text response was recorded.");
|
|
82
|
+
details.appendChild(summary); details.appendChild(body); card.appendChild(details);
|
|
83
|
+
}
|
|
84
|
+
if (item.canRetry) {
|
|
85
|
+
var retryButton = button("pair-result-status-retry", null, null);
|
|
86
|
+
retryButton.textContent = "Retry delivery";
|
|
87
|
+
retryButton.addEventListener("click", retry.bind(null, item));
|
|
88
|
+
card.appendChild(retryButton);
|
|
89
|
+
}
|
|
90
|
+
return card;
|
|
91
|
+
}
|
|
92
|
+
|
|
46
93
|
function renderPairResultStatus() {
|
|
47
94
|
var existing = document.getElementById("pair-result-status");
|
|
95
|
+
var focusedToggle = !!(existing && existing.ownerDocument.activeElement && existing.ownerDocument.activeElement.className === "pair-result-status-toggle");
|
|
48
96
|
if (existing) existing.remove();
|
|
49
97
|
var items = store.get("pairResultStatus") || [];
|
|
50
|
-
if (!items.length) return;
|
|
98
|
+
if (!items.length) { setTrayOpen(false); return; }
|
|
51
99
|
var messages = document.getElementById("messages");
|
|
52
100
|
if (!messages || !messages.parentElement) return;
|
|
101
|
+
var trayOpen = isTrayOpen();
|
|
102
|
+
var state = worstState(items);
|
|
103
|
+
var label = items.length === 1 ? "Worker update" : items.length + " Worker updates";
|
|
53
104
|
var section = document.createElement("section");
|
|
54
105
|
section.id = "pair-result-status";
|
|
55
|
-
section.className = "pair-result-status";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
106
|
+
section.className = "pair-result-status pair-result-status-tone-" + state + (trayOpen ? " is-open" : "");
|
|
107
|
+
var bar = document.createElement("div");
|
|
108
|
+
bar.className = "pair-result-status-bar";
|
|
109
|
+
var toggle = button("pair-result-status-toggle", null, null);
|
|
110
|
+
toggle.setAttribute("aria-expanded", trayOpen ? "true" : "false");
|
|
111
|
+
toggle.setAttribute("aria-controls", "pair-result-status-tray");
|
|
112
|
+
toggle.innerHTML = '<span class="pair-result-status-icon" aria-hidden="true">' + iconHtml(state === "pending" ? "clock" : "triangle-alert") + '</span>' +
|
|
113
|
+
'<span class="pair-result-status-label">' + label + '</span>' +
|
|
114
|
+
'<span class="pair-result-status-state">, ' + stateWords(state) + '.</span>' +
|
|
115
|
+
'<span class="pair-result-status-review">Review</span>' +
|
|
116
|
+
'<span class="pair-result-status-chevron" aria-hidden="true">' + iconHtml("chevron-down") + '</span>';
|
|
117
|
+
// One short polite announcement; the details stay in the tray.
|
|
118
|
+
var live = document.createElement("span");
|
|
119
|
+
live.className = "pair-result-status-live"; live.setAttribute("role", "status"); live.textContent = label;
|
|
120
|
+
bar.appendChild(live);
|
|
121
|
+
var close = button("pair-result-status-dismiss", "Dismiss Worker notices", "x");
|
|
65
122
|
close.title = "Dismiss notices. Retained tasks and results are kept.";
|
|
66
123
|
close.addEventListener("click", dismiss.bind(null, items));
|
|
67
|
-
|
|
68
|
-
var
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
details.appendChild(summary); details.appendChild(body); card.appendChild(details);
|
|
86
|
-
}
|
|
87
|
-
if (item.canRetry) {
|
|
88
|
-
var button = document.createElement("button");
|
|
89
|
-
button.type = "button"; button.className = "pair-result-status-retry"; button.textContent = "Retry delivery";
|
|
90
|
-
button.addEventListener("click", retry.bind(null, item));
|
|
91
|
-
card.appendChild(button);
|
|
92
|
-
}
|
|
93
|
-
content.appendChild(card);
|
|
94
|
-
}
|
|
124
|
+
bar.appendChild(toggle); bar.appendChild(close); section.appendChild(bar);
|
|
125
|
+
var tray = document.createElement("div");
|
|
126
|
+
tray.id = "pair-result-status-tray"; tray.className = "pair-result-status-tray";
|
|
127
|
+
tray.hidden = !trayOpen;
|
|
128
|
+
for (var i = 0; i < items.length; i++) tray.appendChild(renderCard(items[i]));
|
|
129
|
+
section.appendChild(tray);
|
|
130
|
+
toggle.addEventListener("click", function () {
|
|
131
|
+
trayOpen = !isTrayOpen(); setTrayOpen(trayOpen);
|
|
132
|
+
toggle.setAttribute("aria-expanded", trayOpen ? "true" : "false");
|
|
133
|
+
tray.hidden = !trayOpen;
|
|
134
|
+
section.className = section.className.replace(/ is-open/g, "") + (trayOpen ? " is-open" : "");
|
|
135
|
+
});
|
|
136
|
+
section.addEventListener("keydown", function (event) {
|
|
137
|
+
if (event.key !== "Escape" || !isTrayOpen()) return;
|
|
138
|
+
trayOpen = false; setTrayOpen(false); toggle.setAttribute("aria-expanded", "false"); tray.hidden = true;
|
|
139
|
+
section.className = section.className.replace(/ is-open/g, "");
|
|
140
|
+
if (toggle.focus) toggle.focus();
|
|
141
|
+
});
|
|
95
142
|
messages.parentElement.insertBefore(section, messages);
|
|
143
|
+
refreshIcons();
|
|
144
|
+
if (focusedToggle && toggle.focus) toggle.focus();
|
|
96
145
|
}
|
|
97
146
|
|
|
98
147
|
function handlePairResultStatus(msg) {
|
|
@@ -105,6 +154,7 @@ function handlePairResultStatus(msg) {
|
|
|
105
154
|
|
|
106
155
|
function clearPairResultStatus() {
|
|
107
156
|
statusGeneration++;
|
|
157
|
+
setTrayOpen(false);
|
|
108
158
|
store.set({ pairResultStatus: [] });
|
|
109
159
|
renderPairResultStatus();
|
|
110
160
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Live appearance sync between the app shell and its pane iframes (split
|
|
2
|
+
// Driver/Worker panes and the contextual Mate window). Each iframe runs its own
|
|
3
|
+
// theme module and only reads the persisted preference at load, so the shell
|
|
4
|
+
// pushes the applied appearance down on every change and when a frame loads.
|
|
5
|
+
// Same-origin only, shell to child only; nothing is saved from either side.
|
|
6
|
+
|
|
7
|
+
import { store } from './store.js';
|
|
8
|
+
import { onThemeChange, getAppearanceSnapshot, applyExternalAppearance } from './theme.js';
|
|
9
|
+
|
|
10
|
+
var ID = /^[a-zA-Z0-9_-]{1,120}$/;
|
|
11
|
+
|
|
12
|
+
function paneFrames() {
|
|
13
|
+
return Array.prototype.filter.call(document.querySelectorAll('iframe'), function (frame) {
|
|
14
|
+
try { var url = new URL(frame.src, window.location.href); return url.origin === window.location.origin && url.searchParams.get('pane') === '1'; }
|
|
15
|
+
catch (error) { return false; }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function push(frame) {
|
|
20
|
+
var snapshot = getAppearanceSnapshot();
|
|
21
|
+
try { if (frame.contentWindow) frame.contentWindow.postMessage({ type: 'clay-appearance', snapshot: snapshot }, window.location.origin); } catch (error) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isId(value) { return typeof value === 'string' && ID.test(value); }
|
|
25
|
+
function isIdOrNull(value) { return value === null || isId(value); }
|
|
26
|
+
|
|
27
|
+
// Exact shape only: strings or null, never coerced values.
|
|
28
|
+
function validSnapshot(value) {
|
|
29
|
+
var pref = value && typeof value === 'object' ? value.preference : null;
|
|
30
|
+
if (!pref || typeof pref !== 'object' || !isId(value.themeId)) return false;
|
|
31
|
+
if (value.userId !== null && (typeof value.userId !== 'string' || !value.userId || value.userId.length > 200)) return false;
|
|
32
|
+
if (typeof pref.configured !== 'boolean') return false;
|
|
33
|
+
if (pref.mode !== null && pref.mode !== 'light' && pref.mode !== 'dark') return false;
|
|
34
|
+
return isIdOrNull(pref.theme) && isIdOrNull(pref.lightTheme) && isIdOrNull(pref.darkTheme);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function initPaneAppearanceBridge() {
|
|
38
|
+
if (store.get('paneMode')) {
|
|
39
|
+
if (window.parent === window) return;
|
|
40
|
+
window.addEventListener('message', function (event) {
|
|
41
|
+
if (event.origin !== window.location.origin || event.source !== window.parent) return;
|
|
42
|
+
var msg = event.data;
|
|
43
|
+
if (!msg || msg.type !== 'clay-appearance' || !validSnapshot(msg.snapshot)) return;
|
|
44
|
+
// Once this frame knows its owner the snapshot must name exactly that owner (null is a mismatch).
|
|
45
|
+
// Before identity is known, or in solo mode without one, any well-formed snapshot applies.
|
|
46
|
+
var owner = store.get('myUserId') || null;
|
|
47
|
+
if (owner !== null && msg.snapshot.userId !== owner) return;
|
|
48
|
+
applyExternalAppearance(msg.snapshot);
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
onThemeChange(function () { paneFrames().forEach(push); });
|
|
53
|
+
// Frames that rejected an early snapshot (identity not yet known here) get it again once it is.
|
|
54
|
+
store.subscribe(function (state, previous) { if (state.myUserId !== previous.myUserId) paneFrames().forEach(push); });
|
|
55
|
+
// A frame that finished loading may have been seeded before a pending save.
|
|
56
|
+
document.addEventListener('load', function (event) {
|
|
57
|
+
if (event.target && event.target.tagName === 'IFRAME' && paneFrames().indexOf(event.target) >= 0) push(event.target);
|
|
58
|
+
}, true);
|
|
59
|
+
}
|
|
@@ -135,9 +135,12 @@ export function uploadAvatarBlob(blob) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// --- Avatar position picker ---
|
|
138
|
-
|
|
138
|
+
// With a host element the editor mounts inline as a settings subview instead
|
|
139
|
+
// of a page-level overlay.
|
|
140
|
+
export function showAvatarPositioner(img, objectUrl, onDone, host) {
|
|
139
141
|
var outputSize = 256;
|
|
140
142
|
var viewSize = 220;
|
|
143
|
+
var priorFocus = document.activeElement;
|
|
141
144
|
|
|
142
145
|
// State
|
|
143
146
|
var scale = 1;
|
|
@@ -169,7 +172,7 @@ export function showAvatarPositioner(img, objectUrl, onDone) {
|
|
|
169
172
|
|
|
170
173
|
// Build overlay
|
|
171
174
|
var overlay = document.createElement('div');
|
|
172
|
-
overlay.className = 'avatar-positioner-overlay';
|
|
175
|
+
overlay.className = host ? 'avatar-positioner-inline settings-subview' : 'avatar-positioner-overlay';
|
|
173
176
|
|
|
174
177
|
var container = document.createElement('div');
|
|
175
178
|
container.className = 'avatar-positioner-container';
|
|
@@ -225,8 +228,10 @@ export function showAvatarPositioner(img, objectUrl, onDone) {
|
|
|
225
228
|
container.appendChild(btnRow);
|
|
226
229
|
|
|
227
230
|
overlay.appendChild(container);
|
|
228
|
-
|
|
231
|
+
if (host) { host.classList.add('has-subview'); host.appendChild(overlay); overlay.setAttribute('role', 'region'); overlay.setAttribute('aria-label', 'Position your avatar'); }
|
|
232
|
+
else document.body.appendChild(overlay);
|
|
229
233
|
updatePreview();
|
|
234
|
+
if (host) doneFocus();
|
|
230
235
|
|
|
231
236
|
// Drag
|
|
232
237
|
var dragging = false;
|
|
@@ -288,9 +293,12 @@ export function showAvatarPositioner(img, objectUrl, onDone) {
|
|
|
288
293
|
updatePreview();
|
|
289
294
|
}, { passive: false });
|
|
290
295
|
|
|
296
|
+
function doneFocus() { closeBtn.focus(); }
|
|
297
|
+
|
|
291
298
|
function cleanup(revoke) {
|
|
292
299
|
document.removeEventListener('keydown', onOverlayKeydown);
|
|
293
300
|
overlay.remove();
|
|
301
|
+
if (host) { host.classList.remove('has-subview'); if (priorFocus && priorFocus.isConnected) priorFocus.focus(); }
|
|
294
302
|
if (revoke) URL.revokeObjectURL(objectUrl);
|
|
295
303
|
}
|
|
296
304
|
|
|
@@ -81,6 +81,9 @@ function startMateCreation() {
|
|
|
81
81
|
maybeStartPendingCreation();
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// Entry for the zero-project welcome: the same Clay-guided flow the add button's Mate choice runs.
|
|
85
|
+
export function startClayMateCreation() { startMateCreation(); }
|
|
86
|
+
|
|
84
87
|
function maybeStartPendingCreation() {
|
|
85
88
|
var state = store.snap();
|
|
86
89
|
if (!state.projectMateCreationPendingStart || !state.connected || state.activeProjectSlug !== 'mate-' + state.projectMateCreationMateId) return;
|
|
@@ -89,11 +92,17 @@ function maybeStartPendingCreation() {
|
|
|
89
92
|
store.set({ projectMateCreationPendingStart: false, projectMateCreationAwaitingHistory: true, projectMateCreationRequestId: requestId });
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
function choiceButton(
|
|
95
|
+
function choiceButton(artwork, title, description) {
|
|
93
96
|
var button = document.createElement('button');
|
|
94
97
|
button.type = 'button';
|
|
95
98
|
button.className = 'project-create-choice-option';
|
|
96
|
-
|
|
99
|
+
var image = document.createElement('img');
|
|
100
|
+
image.src = '/welcome-art/' + artwork + '.png';
|
|
101
|
+
image.alt = '';
|
|
102
|
+
image.width = 1536;
|
|
103
|
+
image.height = 1024;
|
|
104
|
+
image.decoding = 'async';
|
|
105
|
+
button.appendChild(image);
|
|
97
106
|
var copy = document.createElement('span');
|
|
98
107
|
var heading = document.createElement('strong');
|
|
99
108
|
heading.textContent = title;
|
|
@@ -121,9 +130,9 @@ export function openProjectCreateChoice(opener, openProject) {
|
|
|
121
130
|
var title = document.createElement('h2');
|
|
122
131
|
title.id = 'project-create-choice-title';
|
|
123
132
|
title.textContent = 'What would you like to add?';
|
|
124
|
-
var mate = choiceButton('
|
|
133
|
+
var mate = choiceButton('mate-clay', 'Create a Mate', 'A specialist to work alongside you.');
|
|
125
134
|
mate.addEventListener('click', startMateCreation);
|
|
126
|
-
var project = choiceButton('
|
|
135
|
+
var project = choiceButton('project-clay', 'Start a project', 'Work on code or files together.');
|
|
127
136
|
project.addEventListener('click', function () {
|
|
128
137
|
closeChoice(false);
|
|
129
138
|
openProject();
|
|
@@ -27,12 +27,10 @@ function setSelectState(select, value, disabled) {
|
|
|
27
27
|
select.disabled = !!disabled;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
30
|
+
// Editable for any authorised selected project: the server re-authorises the
|
|
31
|
+
// exact slug (owner, permission, access, worktree) and echoes it back.
|
|
34
32
|
function isReadOnly(active) {
|
|
35
|
-
return !active || !active.authorized || active.isWorktree
|
|
33
|
+
return !active || !active.authorized || active.isWorktree;
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
function renderActiveState() {
|
|
@@ -40,23 +38,26 @@ function renderActiveState() {
|
|
|
40
38
|
var pending = pendingState();
|
|
41
39
|
var select = selectElement();
|
|
42
40
|
var inheritedHint = document.getElementById("ps-session-visibility-default-inherited-hint");
|
|
43
|
-
var nonCurrentHint = document.getElementById("ps-session-visibility-default-non-current-hint");
|
|
44
41
|
if (inheritedHint) inheritedHint.classList.toggle("hidden", !(active && active.isWorktree));
|
|
45
|
-
if (nonCurrentHint) nonCurrentHint.classList.toggle("hidden", !(active && active.authorized && !active.isWorktree && !isCurrentProject(active)));
|
|
46
42
|
if (!select || !active) return;
|
|
47
|
-
|
|
43
|
+
// One request at a time: a pending change for any project holds the control.
|
|
44
|
+
setSelectState(select, active.effectiveValue, isReadOnly(active) || !!pending);
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
|
|
47
|
+
// The server requires the project owner plus the projectSettings permission.
|
|
48
|
+
// An administrator who is not the owner may see the value but not change it.
|
|
49
|
+
function visibilityAccess(project) {
|
|
51
50
|
var users = store.get('cachedAllUsers') || [];
|
|
52
51
|
var userId = store.get('myUserId');
|
|
53
52
|
var currentUser = null;
|
|
54
53
|
for (var i = 0; i < users.length; i++) {
|
|
55
54
|
if (users[i].id === userId) { currentUser = users[i]; break; }
|
|
56
55
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
var permitted = !store.get('permissions') || store.get('permissions').projectSettings !== false;
|
|
57
|
+
var owner = !!(project && project.projectOwnerId && project.projectOwnerId === userId);
|
|
58
|
+
var admin = !!(currentUser && currentUser.role === "admin");
|
|
59
|
+
var multi = !!(store.get('isMultiUserMode') && project && permitted);
|
|
60
|
+
return { visible: multi && (owner || admin), editable: multi && owner };
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function initProjectSessionVisibilitySettings() {
|
|
@@ -80,7 +81,6 @@ export function initProjectSessionVisibilitySettings() {
|
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
store.subscribe(function (state, previous) {
|
|
83
|
-
if (state.currentSlug !== previous.currentSlug) renderActiveState();
|
|
84
84
|
if (state.connected || previous.connected !== true) return;
|
|
85
85
|
var pending = pendingState();
|
|
86
86
|
if (!pending) return;
|
|
@@ -95,10 +95,13 @@ export function initProjectSessionVisibilitySettings() {
|
|
|
95
95
|
export function populateProjectSessionVisibilitySettings(slug, project) {
|
|
96
96
|
var field = document.getElementById("ps-session-visibility-default-field");
|
|
97
97
|
var select = selectElement();
|
|
98
|
-
|
|
98
|
+
var access = visibilityAccess(project);
|
|
99
|
+
if (field) field.style.display = access.visible ? "" : "none";
|
|
100
|
+
var ownerHint = document.getElementById("ps-session-visibility-default-owner-hint");
|
|
101
|
+
if (ownerHint) ownerHint.classList.toggle("hidden", !(access.visible && !access.editable));
|
|
99
102
|
if (!select) return;
|
|
100
103
|
var value = project && project.sessionVisibilityDefault === "shared" ? "shared" : "private";
|
|
101
|
-
var active = { slug: slug || "", effectiveValue: value, isWorktree: !!(project && project.isWorktree), authorized:
|
|
104
|
+
var active = { slug: slug || "", effectiveValue: value, isWorktree: !!(project && project.isWorktree), authorized: access.editable };
|
|
102
105
|
var pending = pendingState();
|
|
103
106
|
setVisibilityState(active, pending);
|
|
104
107
|
renderActiveState();
|
|
@@ -124,7 +127,7 @@ export function handleProjectSessionVisibilityDefault(msg) {
|
|
|
124
127
|
var active = activeState();
|
|
125
128
|
setVisibilityState(active, null);
|
|
126
129
|
var select = selectElement();
|
|
127
|
-
if (!select || !active || active.slug !== msg.slug) return;
|
|
130
|
+
if (!select || !active || active.slug !== msg.slug) { renderActiveState(); return; }
|
|
128
131
|
if (!msg.ok) {
|
|
129
132
|
active = Object.assign({}, active, { effectiveValue: pending.previous });
|
|
130
133
|
setVisibilityState(active, null);
|
|
@@ -31,7 +31,18 @@ export function handleProjectEnv(msg) {
|
|
|
31
31
|
update({ loaded: true, saved: msg.envrc || '', rows: parseEnvString(msg.envrc || '') });
|
|
32
32
|
render(); status('');
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
// A draft exists only while the rows differ from the saved variables; blank rows and edits that are
|
|
35
|
+
// reverted by hand leave nothing to save.
|
|
36
|
+
function rowsSignature(rows) {
|
|
37
|
+
return (rows || []).filter(function (row) { return row.key.trim() || row.value; }).map(function (row) { return row.key.trim() + '\u0000' + row.value; }).join('\n');
|
|
38
|
+
}
|
|
39
|
+
function changeRows(rows) {
|
|
40
|
+
var dirty = rowsSignature(rows) !== rowsSignature(parseEnvString(state().saved || ''));
|
|
41
|
+
update({ rows: rows, dirty: dirty });
|
|
42
|
+
document.getElementById('ps-env-save').disabled = !dirty || !!state().saving;
|
|
43
|
+
if (!dirty) clearCachedDraft();
|
|
44
|
+
status(dirty ? 'Unsaved changes' : '');
|
|
45
|
+
}
|
|
35
46
|
function render() {
|
|
36
47
|
var editor = state();
|
|
37
48
|
var list = document.getElementById('ps-env-list'); list.replaceChildren();
|