clay-server 5.3.0-beta.6 → 5.3.0
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/lib/project-admin-overview.js +22 -11
- package/lib/project-background-tasks.js +76 -0
- package/lib/project-connection.js +1 -1
- package/lib/project-sessions.js +12 -5
- package/lib/project-split-views.js +12 -3
- package/lib/project-view-store.js +7 -5
- package/lib/project-worker-conversation.js +5 -0
- package/lib/public/app.js +7 -0
- package/lib/public/css/contextual-clay.css +2 -1
- package/lib/public/css/mate-conversation.css +1 -1
- package/lib/public/css/pending-message-queue.css +24 -19
- package/lib/public/css/project-admin-overview.css +42 -13
- package/lib/public/css/shared-browser.css +31 -8
- package/lib/public/css/split-view-layout.css +92 -7
- package/lib/public/css/tool-pane.css +4 -7
- package/lib/public/index.html +5 -5
- package/lib/public/modules/agent-message-bubbles.js +96 -0
- package/lib/public/modules/app-header.js +6 -0
- package/lib/public/modules/app-messages.js +26 -9
- package/lib/public/modules/background-task-previews.js +8 -0
- package/lib/public/modules/background-task-viewer.js +97 -0
- package/lib/public/modules/background-tasks-ui.js +34 -125
- package/lib/public/modules/chat-bubble-renderer.js +11 -5
- package/lib/public/modules/contextual-clay-geometry.js +7 -3
- package/lib/public/modules/pane-activation.js +5 -3
- package/lib/public/modules/pane-header-tooltips.js +32 -0
- package/lib/public/modules/pending-message-queue-drag.js +69 -0
- package/lib/public/modules/pending-message-queue.js +3 -33
- package/lib/public/modules/permission-control.js +6 -5
- package/lib/public/modules/project-admin-overview.js +125 -10
- package/lib/public/modules/project-grouping.js +9 -0
- package/lib/public/modules/server-settings-controls.js +33 -4
- package/lib/public/modules/session-folder-toolbar.js +1 -1
- package/lib/public/modules/session-tool-item.js +86 -0
- package/lib/public/modules/settings-select.js +2 -1
- package/lib/public/modules/shared-browser-tabs.js +37 -21
- package/lib/public/modules/sidebar-projects.js +1 -10
- package/lib/public/modules/split-session-empty-pane.js +47 -0
- package/lib/public/modules/split-view-create-menu.js +74 -65
- package/lib/public/modules/split-view-layout-scope.js +4 -0
- package/lib/public/modules/split-view-picker-actions.js +71 -0
- package/lib/public/modules/split-view-picker-render.js +81 -0
- package/lib/public/modules/split-view-presentation-renderer.js +8 -8
- package/lib/public/modules/split-view-worker-activity.js +2 -2
- package/lib/public/modules/split-view-worker-transcript.js +70 -33
- package/lib/public/modules/split-view-workers.js +6 -4
- package/lib/public/modules/terminal-viewport.js +14 -0
- package/lib/public/modules/terminal.js +3 -4
- package/lib/public/modules/theme-palette.js +177 -0
- package/lib/public/modules/theme.js +1 -174
- package/lib/public/modules/tool-pane-chrome.js +7 -1
- package/lib/public/modules/tools.js +5 -89
- package/lib/public/modules/tooltip.js +32 -6
- package/lib/public/modules/wireframe-workbench.js +2 -2
- package/lib/public/modules/worker-message-previews.js +39 -0
- package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
- package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
- package/lib/public/pane-assets/{chunk-BMHTBRMO.js → chunk-7OVNC5HT.js} +1 -1
- package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
- package/lib/public/pane-assets/{chunk-2AIWCI6Y.js → chunk-EMLN2Y7L.js} +1 -1
- package/lib/public/pane-assets/{chunk-VKYBAVWT.js → chunk-ENPAHXLB.js} +1 -1
- package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
- package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
- package/lib/public/pane-assets/{chunk-6NYCNWVF.js → chunk-FQTNR4ZS.js} +1 -1
- package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
- package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
- package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
- package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
- package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
- package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
- package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
- package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
- package/lib/public/pane-assets/{chunk-PV4PCH2V.js → chunk-SJOT74VW.js} +1 -1
- package/lib/public/pane-assets/{chunk-LDIZLKB2.js → chunk-SJTGHIN5.js} +1 -1
- package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
- package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
- package/lib/public/pane-assets/{chunk-VNAV23CM.js → chunk-UPXU2IPC.js} +1 -1
- package/lib/public/pane-assets/{chunk-TMGSDFJI.js → chunk-VEBYD7TL.js} +1 -1
- package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
- package/lib/public/pane-assets/manifest.json +411 -399
- package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
- package/lib/sdk-message-processor.js +5 -2
- package/lib/server-storage.js +27 -3
- package/lib/server.js +4 -2
- package/lib/sessions.js +1 -1
- package/lib/storage-usage.js +29 -7
- package/lib/terminal-manager.js +2 -2
- package/lib/worker-message-previews.js +50 -0
- package/lib/ws-schema.js +1 -1
- package/lib/yoke/adapters/claude-worker.js +8 -0
- package/lib/yoke/adapters/claude.js +17 -2
- package/lib/yoke/claude-task-reader.js +94 -0
- package/lib/yoke/interface.js +1 -0
- package/package.json +1 -1
- package/lib/public/pane-assets/app-7UZ727NA.js +0 -1
- package/lib/public/pane-assets/chunk-6MIHIQ2W.js +0 -1
- package/lib/public/pane-assets/chunk-6UJ2ALOB.js +0 -1
- package/lib/public/pane-assets/chunk-6XK2OATZ.js +0 -1
- package/lib/public/pane-assets/chunk-BKIDR2ME.js +0 -1
- package/lib/public/pane-assets/chunk-GEFCFZEN.js +0 -1
- package/lib/public/pane-assets/chunk-GN2NQEVC.js +0 -2
- package/lib/public/pane-assets/chunk-IMLMBPT4.js +0 -166
- package/lib/public/pane-assets/chunk-K5S6AILM.js +0 -1
- package/lib/public/pane-assets/chunk-OWNNLZ36.js +0 -1
- package/lib/public/pane-assets/chunk-P2C3LOWL.js +0 -3
- package/lib/public/pane-assets/chunk-R4LMPTKU.js +0 -1
- package/lib/public/pane-assets/chunk-VWTHIQCE.js +0 -1
- package/lib/public/pane-assets/chunk-WM3K5AFH.js +0 -1
- package/lib/public/pane-assets/chunk-XOB3O327.js +0 -1
- package/lib/public/pane-assets/chunk-XOOUCFYT.js +0 -1
- package/lib/public/pane-assets/style-ZM7OOU3M.css +0 -1
|
@@ -1,21 +1,136 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Restricted projects use public shell primitives without opening a project socket.
|
|
2
|
+
import { createStore, store } from './store.js';
|
|
3
|
+
import { groupProjects, getProjectAbbrev } from './project-grouping.js';
|
|
4
|
+
import { computeVars, validateTheme, defaultDarkFallback, defaultLightFallback } from './theme-palette.js';
|
|
5
|
+
import { initTooltips } from './tooltip.js';
|
|
6
|
+
|
|
7
|
+
var data = JSON.parse(document.getElementById('project-admin-data').textContent);
|
|
8
|
+
createStore({ adminOverview: data, adminUsage: null, adminUsageLoading: false, adminUsageTimer: null, adminUsageGeneration: 0, adminRailCollapsed: {}, adminThemes: null });
|
|
9
|
+
|
|
10
|
+
function applyAppearance() {
|
|
11
|
+
var seed = window.__clayAppearanceSeed;
|
|
12
|
+
var mode = seed && seed.configured && seed.mode || (matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark');
|
|
13
|
+
var id = seed && (seed[mode + 'Theme'] || seed.theme);
|
|
14
|
+
var themes = store.get('adminThemes') || {};
|
|
15
|
+
var theme = themes[id] && themes[id].variant === mode ? themes[id] : (mode === 'light' ? defaultLightFallback : defaultDarkFallback);
|
|
16
|
+
if (!validateTheme(theme)) return;
|
|
17
|
+
var vars = computeVars(theme);
|
|
18
|
+
Object.keys(vars).forEach(function (name) { document.documentElement.style.setProperty(name, vars[name]); });
|
|
19
|
+
document.documentElement.classList.toggle('light-theme', theme.variant === 'light');
|
|
20
|
+
document.documentElement.classList.toggle('dark-theme', theme.variant !== 'light');
|
|
21
|
+
}
|
|
22
|
+
applyAppearance();
|
|
23
|
+
fetch('/api/themes', { credentials: 'same-origin' }).then(function (response) { if (!response.ok) throw new Error('Themes unavailable'); return response.json(); }).then(function (themes) {
|
|
24
|
+
var valid = {};
|
|
25
|
+
Object.entries(Object.assign({}, themes.bundled, themes.custom)).forEach(function (entry) { if (validateTheme(entry[1])) valid[entry[0]] = entry[1]; });
|
|
26
|
+
store.set({ adminThemes: valid }); applyAppearance();
|
|
27
|
+
}).catch(function () {});
|
|
28
|
+
matchMedia('(prefers-color-scheme: light)').addEventListener('change', applyAppearance);
|
|
29
|
+
|
|
30
|
+
function projectLink(project, worktree) {
|
|
31
|
+
var link = document.createElement('a');
|
|
32
|
+
link.className = (worktree ? 'icon-strip-wt-item' : 'icon-strip-item') + (project.slug === data.project.slug ? ' active' : '');
|
|
33
|
+
link.href = '/p/' + encodeURIComponent(project.slug) + '/';
|
|
34
|
+
link.dataset.slug = project.slug;
|
|
35
|
+
link.dataset.tip = project.title + (project.contentAccess === false ? ' · Administrative overview' : '');
|
|
36
|
+
link.setAttribute('aria-label', link.dataset.tip);
|
|
37
|
+
if (project.slug === data.project.slug) link.setAttribute('aria-current', 'page');
|
|
38
|
+
var label = document.createElement('span');
|
|
39
|
+
label.className = worktree ? 'wt-branch-abbrev' : 'icon-strip-name';
|
|
40
|
+
if (project.icon) label.classList.add('project-emoji');
|
|
41
|
+
label.textContent = project.icon || getProjectAbbrev(project.title);
|
|
42
|
+
link.appendChild(label);
|
|
43
|
+
return link;
|
|
44
|
+
}
|
|
45
|
+
function renderNavigation() {
|
|
46
|
+
var container = document.getElementById('icon-strip-projects');
|
|
47
|
+
var grouped = groupProjects(data.projects);
|
|
48
|
+
grouped.parents.forEach(function (project) {
|
|
49
|
+
var children = grouped.wtByParent[project.slug] || [];
|
|
50
|
+
var link = projectLink(project, false);
|
|
51
|
+
if (!children.length) { container.appendChild(link); return; }
|
|
52
|
+
var group = document.createElement('div');
|
|
53
|
+
group.className = 'icon-strip-group'; group.dataset.parentSlug = project.slug;
|
|
54
|
+
var header = document.createElement('div'); header.className = 'project-admin-rail-header';
|
|
55
|
+
header.appendChild(link); link.classList.add('folder-header');
|
|
56
|
+
var toggle = document.createElement('button');
|
|
57
|
+
toggle.type = 'button'; toggle.className = 'icon-strip-group-chevron'; toggle.dataset.tip = 'Toggle worktrees';
|
|
58
|
+
toggle.setAttribute('aria-label', 'Toggle ' + project.title + ' worktrees'); toggle.setAttribute('aria-expanded', 'true');
|
|
59
|
+
var list = document.createElement('div'); list.className = 'icon-strip-group-items'; list.id = 'worktrees-' + project.slug;
|
|
60
|
+
toggle.setAttribute('aria-controls', list.id);
|
|
61
|
+
toggle.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 3v12a6 6 0 0 0 12 0V6M6 7h0"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="6" r="3"/></svg>';
|
|
62
|
+
toggle.addEventListener('click', function () {
|
|
63
|
+
var collapsed = group.classList.toggle('collapsed'); list.inert = collapsed;
|
|
64
|
+
toggle.setAttribute('aria-expanded', String(!collapsed));
|
|
65
|
+
var state = Object.assign({}, store.get('adminRailCollapsed')); state[project.slug] = collapsed; store.set({ adminRailCollapsed: state });
|
|
66
|
+
});
|
|
67
|
+
children.forEach(function (child) { list.appendChild(projectLink(child, true)); });
|
|
68
|
+
header.appendChild(toggle); group.append(header, list); container.appendChild(group);
|
|
69
|
+
});
|
|
70
|
+
var parent = data.projects.find(function (project) { return project.slug === data.project.parentSlug; });
|
|
71
|
+
var label = document.getElementById('project-admin-parent');
|
|
72
|
+
if (parent) { label.hidden = false; label.textContent = 'Worktree of ' + parent.title; }
|
|
73
|
+
}
|
|
74
|
+
renderNavigation();
|
|
75
|
+
initTooltips();
|
|
76
|
+
|
|
2
77
|
var tabs = Array.from(document.querySelectorAll('[role="tab"]'));
|
|
3
78
|
function select(tab) {
|
|
4
79
|
tabs.forEach(function (item) {
|
|
5
80
|
var active = item === tab;
|
|
6
|
-
item.setAttribute(
|
|
7
|
-
item.
|
|
8
|
-
document.getElementById(item.getAttribute("aria-controls")).hidden = !active;
|
|
81
|
+
item.setAttribute('aria-selected', String(active)); item.tabIndex = active ? 0 : -1;
|
|
82
|
+
document.getElementById(item.getAttribute('aria-controls')).hidden = !active;
|
|
9
83
|
});
|
|
10
84
|
}
|
|
11
85
|
tabs.forEach(function (tab, index) {
|
|
12
|
-
tab.addEventListener(
|
|
13
|
-
tab.addEventListener(
|
|
14
|
-
if ([
|
|
86
|
+
tab.addEventListener('click', function () { select(tab); });
|
|
87
|
+
tab.addEventListener('keydown', function (event) {
|
|
88
|
+
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].indexOf(event.key) === -1) return;
|
|
15
89
|
event.preventDefault();
|
|
16
|
-
var target = event.key ===
|
|
17
|
-
select(target);
|
|
18
|
-
target.focus();
|
|
90
|
+
var target = event.key === 'Home' ? tabs[0] : event.key === 'End' ? tabs[tabs.length - 1] : tabs[(index + (event.key === 'ArrowRight' ? 1 : tabs.length - 1)) % tabs.length];
|
|
91
|
+
select(target); target.focus();
|
|
19
92
|
});
|
|
20
93
|
});
|
|
21
94
|
select(tabs[0]);
|
|
95
|
+
|
|
96
|
+
function size(bytes) {
|
|
97
|
+
if (!Number.isFinite(bytes) || bytes < 0) return 'Unavailable';
|
|
98
|
+
var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'], index = 0;
|
|
99
|
+
while (bytes >= 1024 && index < units.length - 1) { bytes /= 1024; index++; }
|
|
100
|
+
return bytes.toFixed(index ? 1 : 0) + ' ' + units[index];
|
|
101
|
+
}
|
|
102
|
+
function clearUsage() {
|
|
103
|
+
['bytes', 'files', 'free', 'capacity'].forEach(function (field) { document.getElementById('usage-' + field).textContent = '—'; });
|
|
104
|
+
store.set({ adminUsage: null });
|
|
105
|
+
}
|
|
106
|
+
async function loadUsage(refresh) {
|
|
107
|
+
if (store.get('adminUsageLoading')) return;
|
|
108
|
+
clearTimeout(store.get('adminUsageTimer'));
|
|
109
|
+
var generation = store.get('adminUsageGeneration');
|
|
110
|
+
store.set({ adminUsageLoading: true });
|
|
111
|
+
var button = document.getElementById('project-admin-refresh'), status = document.getElementById('usage-status');
|
|
112
|
+
button.disabled = true; document.getElementById('project-admin-usage').setAttribute('aria-busy', 'true');
|
|
113
|
+
try {
|
|
114
|
+
var response = await fetch('/api/server/storage/project/' + encodeURIComponent(data.project.slug) + (refresh ? '?refresh=1' : ''), { credentials: 'same-origin' });
|
|
115
|
+
var result = await response.json();
|
|
116
|
+
if (generation !== store.get('adminUsageGeneration')) return;
|
|
117
|
+
if (!response.ok) { if ([401, 403, 404, 409].indexOf(response.status) >= 0) clearUsage(); throw new Error(result.error || 'Storage measurement failed.'); }
|
|
118
|
+
if (result.slug !== data.project.slug) throw new Error('Storage response belongs to another project.');
|
|
119
|
+
store.set({ adminUsage: result });
|
|
120
|
+
var usage = result.usage;
|
|
121
|
+
if (usage && usage.available) {
|
|
122
|
+
document.getElementById('usage-bytes').textContent = (result.partial || result.scanning ? 'At least ' : '') + size(usage.bytes);
|
|
123
|
+
document.getElementById('usage-files').textContent = (result.partial || result.scanning ? 'At least ' : '') + Number(usage.files).toLocaleString();
|
|
124
|
+
document.getElementById('usage-free').textContent = size(usage.availableBytes);
|
|
125
|
+
document.getElementById('usage-capacity').textContent = size(usage.capacityBytes);
|
|
126
|
+
} else clearUsage();
|
|
127
|
+
status.textContent = result.scanning ? 'Scan in progress. Totals are not final.' : !usage || !usage.available ? 'Storage is unavailable: the directory is missing, unreadable or shared by multiple registrations.' : (result.partial ? 'Partial scan; some paths could not be measured. ' : '') + 'Measured ' + new Date(result.checkedAt).toLocaleString() + '. Cached for up to 5 minutes.';
|
|
128
|
+
if (result.scanning && !document.hidden) store.set({ adminUsageTimer: setTimeout(function () { loadUsage(false); }, 1500) });
|
|
129
|
+
} catch (error) { if (generation === store.get('adminUsageGeneration')) status.textContent = error.message + (store.get('adminUsage') ? ' Previous measurement retained.' : ''); }
|
|
130
|
+
finally { if (generation === store.get('adminUsageGeneration')) { store.set({ adminUsageLoading: false }); button.disabled = false; document.getElementById('project-admin-usage').setAttribute('aria-busy', 'false'); } }
|
|
131
|
+
}
|
|
132
|
+
document.getElementById('project-admin-refresh').addEventListener('click', function () { loadUsage(true); });
|
|
133
|
+
document.addEventListener('visibilitychange', function () { if (document.hidden) clearTimeout(store.get('adminUsageTimer')); else if (store.get('adminUsage') && store.get('adminUsage').scanning) loadUsage(false); });
|
|
134
|
+
window.addEventListener('pagehide', function () { clearTimeout(store.get('adminUsageTimer')); store.set({ adminUsageGeneration: store.get('adminUsageGeneration') + 1 }); });
|
|
135
|
+
window.addEventListener('pageshow', function (event) { if (event.persisted) { clearUsage(); store.set({ adminUsageLoading: false }); loadUsage(false); } });
|
|
136
|
+
loadUsage(false);
|
|
@@ -20,3 +20,12 @@ export function groupProjects(projects) {
|
|
|
20
20
|
}
|
|
21
21
|
return { parents: parents, wtByParent: wtByParent };
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
export function getProjectAbbrev(name) {
|
|
25
|
+
if (!name) return "?";
|
|
26
|
+
var words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
|
|
27
|
+
if (words.length >= 2) {
|
|
28
|
+
return (words[0][0] + words[1][0]).toUpperCase();
|
|
29
|
+
}
|
|
30
|
+
return name.substring(0, 2).toUpperCase();
|
|
31
|
+
}
|
|
@@ -4,6 +4,23 @@ import { showToast } from './utils.js';
|
|
|
4
4
|
import { confirmSettingsAction, clearSettingsDraft, setSettingsDraft } from './server-settings-state.js';
|
|
5
5
|
import { enhanceSettingsSelects } from './settings-select.js';
|
|
6
6
|
|
|
7
|
+
var controlSettings = {
|
|
8
|
+
set_keep_awake: { config: 'keepAwake', field: 'value' },
|
|
9
|
+
set_image_retention: { config: 'imageRetentionDays', field: 'days' },
|
|
10
|
+
set_update_channel: { config: 'updateChannel', field: 'channel' }
|
|
11
|
+
};
|
|
12
|
+
function controlValue(input, type) {
|
|
13
|
+
if (type === 'set_image_retention') return Number(input.value);
|
|
14
|
+
if (type === 'set_update_channel') return input.checked ? 'beta' : 'stable';
|
|
15
|
+
return input.checked;
|
|
16
|
+
}
|
|
17
|
+
function savedControlValue(type) {
|
|
18
|
+
var value = (store.get('serverSettingsConfig') || {})[controlSettings[type].config];
|
|
19
|
+
if (type === 'set_image_retention') return value === undefined ? 7 : Number(value);
|
|
20
|
+
if (type === 'set_update_channel') return value === 'beta' ? 'beta' : 'stable';
|
|
21
|
+
return !!value;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
function status(id, text) {
|
|
8
25
|
var el = document.getElementById(id);
|
|
9
26
|
if (el) { el.textContent = text; el.classList.remove('hidden'); }
|
|
@@ -19,7 +36,7 @@ function sendSetting(type, values, buttonId, statusId) {
|
|
|
19
36
|
if (input) { input.disabled = true; input.dispatchEvent(new Event('settings-sync')); }
|
|
20
37
|
status(statusId, 'Saving…');
|
|
21
38
|
var pending = Object.assign({}, store.get('serverSettingsPending') || {});
|
|
22
|
-
pending[type] = { buttonId: buttonId, statusId: statusId };
|
|
39
|
+
pending[type] = { buttonId: buttonId, statusId: statusId, submitted: controlSettings[type] ? values[controlSettings[type].field] : null };
|
|
23
40
|
store.set({ serverSettingsPending: pending });
|
|
24
41
|
ws.send(JSON.stringify(Object.assign({ type: type }, values)));
|
|
25
42
|
setTimeout(function () {
|
|
@@ -51,14 +68,25 @@ export function handleServerSettingResult(msg) {
|
|
|
51
68
|
var input = request.buttonId === 'settings-pin-save-btn' ? document.getElementById('settings-pin-input') : request.buttonId.endsWith('-save') ? document.getElementById(request.buttonId.slice(0, -5)) : null;
|
|
52
69
|
if (input) { input.disabled = false; input.dispatchEvent(new Event('settings-sync')); }
|
|
53
70
|
status(request.statusId, msg.ok ? 'Saved. ' + (msg.timing || '') : (msg.error || 'Could not save. Retry your change.'));
|
|
54
|
-
if (msg.ok)
|
|
71
|
+
if (msg.ok) {
|
|
72
|
+
var setting = controlSettings[type];
|
|
73
|
+
if (setting) {
|
|
74
|
+
var config = Object.assign({}, store.get('serverSettingsConfig') || {});
|
|
75
|
+
config[setting.config] = request.submitted;
|
|
76
|
+
store.set({ serverSettingsConfig: config });
|
|
77
|
+
var dirty = input && controlValue(input, type) !== savedControlValue(type);
|
|
78
|
+
setSettingsDraft(type, dirty);
|
|
79
|
+
if (button) button.hidden = !dirty;
|
|
80
|
+
if (dirty) status(request.statusId, 'Submitted value saved. Newer edits remain unsaved.');
|
|
81
|
+
} else clearSettingsDraft(type);
|
|
82
|
+
}
|
|
55
83
|
}
|
|
56
84
|
|
|
57
85
|
function finishSetting(msg, id, field) {
|
|
58
86
|
handleServerSettingResult(msg);
|
|
59
87
|
var el = document.getElementById(id);
|
|
60
88
|
var key = 'set_keep_awake';
|
|
61
|
-
if (
|
|
89
|
+
if ((store.get('serverSettingsDrafts') || {})[key]) return;
|
|
62
90
|
if (el && msg.ok !== false && msg[field] !== undefined) el.checked = !!msg[field];
|
|
63
91
|
}
|
|
64
92
|
|
|
@@ -108,7 +136,8 @@ export function initServerControls() {
|
|
|
108
136
|
var save = document.createElement('button'); save.type = 'button'; save.className = 'settings-btn-sm'; save.textContent = 'Save changes';
|
|
109
137
|
save.id = entry[0] + '-save'; save.hidden = true; receipt.insertAdjacentElement('beforebegin', save);
|
|
110
138
|
input.onchange = function () {
|
|
111
|
-
|
|
139
|
+
var dirty = controlValue(input, entry[1]) !== savedControlValue(entry[1]);
|
|
140
|
+
setSettingsDraft(entry[1], dirty); save.hidden = !dirty; receipt.textContent = dirty ? entry[3] : 'No unsaved changes';
|
|
112
141
|
};
|
|
113
142
|
save.onclick = function () {
|
|
114
143
|
var values = {}; values[entry[2]] = entry[2] === 'days' ? Number(input.value) : entry[2] === 'channel' ? (input.checked ? 'beta' : 'stable') : input.checked;
|
|
@@ -165,7 +165,7 @@ export function renderFolderToolbar(surface, displayCounts) {
|
|
|
165
165
|
view.setAttribute("aria-haspopup", "true");
|
|
166
166
|
var viewState = store.get('sessionFolderViewMenu');
|
|
167
167
|
view.setAttribute("aria-expanded", String(!!(viewState && viewState.surface === surface)));
|
|
168
|
-
view.innerHTML = iconHtml("sliders-horizontal") + "<span>View</span>";
|
|
168
|
+
view.innerHTML = iconHtml("sliders-horizontal") + "<span>View options</span>";
|
|
169
169
|
view.addEventListener("click", function () { toggleViewMenu(surface); });
|
|
170
170
|
|
|
171
171
|
var magnifier = document.createElement("button");
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { clayToolGroupSummary } from './clay-tool-presentation.js';
|
|
2
|
+
import { iconHtml } from './icons.js';
|
|
3
|
+
|
|
4
|
+
// Shared stateless activity chrome for live sessions and read-only transcripts.
|
|
5
|
+
export function createSessionToolItem(id, name) {
|
|
6
|
+
var el = document.createElement("div");
|
|
7
|
+
el.className = "tool-item";
|
|
8
|
+
if (id != null) el.dataset.toolId = id;
|
|
9
|
+
el.innerHTML =
|
|
10
|
+
'<div class="tool-header">' +
|
|
11
|
+
'<span class="tool-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
12
|
+
'<span class="tool-bullet"></span>' +
|
|
13
|
+
'<span class="tool-name"></span>' +
|
|
14
|
+
'<span class="tool-desc"></span>' +
|
|
15
|
+
'<span class="tool-status-icon">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
16
|
+
'</div>' +
|
|
17
|
+
'<div class="tool-subtitle">' +
|
|
18
|
+
'<span class="tool-connector">└</span>' +
|
|
19
|
+
'<span class="tool-subtitle-text">Running...</span>' +
|
|
20
|
+
'</div>';
|
|
21
|
+
|
|
22
|
+
el.querySelector(".tool-name").textContent = name;
|
|
23
|
+
|
|
24
|
+
return el;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createSessionToolGroup(avatarUrl) {
|
|
28
|
+
var group = document.createElement('div'); group.className = 'tool-group';
|
|
29
|
+
var parent = group;
|
|
30
|
+
if (avatarUrl) {
|
|
31
|
+
var image = document.createElement('img'); image.className = 'dm-bubble-avatar dm-bubble-avatar-mate'; image.src = avatarUrl; image.alt = ''; group.appendChild(image);
|
|
32
|
+
parent = document.createElement('div'); parent.className = 'dm-bubble-content'; group.appendChild(parent);
|
|
33
|
+
}
|
|
34
|
+
parent.innerHTML = '<div class="tool-group-header" style="display:none"><span class="tool-group-chevron">' + iconHtml('chevron-right') + '</span><span class="tool-group-bullet"></span><span class="tool-group-label">Running...</span><span class="tool-group-status-icon">' + iconHtml('loader', 'icon-spin') + '</span></div><div class="tool-group-items"></div>';
|
|
35
|
+
return group;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function sessionToolGroupSummary(group, tools) {
|
|
39
|
+
var claySummary = clayToolGroupSummary(group, tools);
|
|
40
|
+
if (claySummary) return claySummary;
|
|
41
|
+
var names = group.toolNames;
|
|
42
|
+
var count = names.length;
|
|
43
|
+
var allDone = group.doneCount >= count;
|
|
44
|
+
|
|
45
|
+
// Count by tool name
|
|
46
|
+
var counts = {};
|
|
47
|
+
for (var i = 0; i < names.length; i++) {
|
|
48
|
+
counts[names[i]] = (counts[names[i]] || 0) + 1;
|
|
49
|
+
}
|
|
50
|
+
var uniqueNames = Object.keys(counts);
|
|
51
|
+
|
|
52
|
+
if (uniqueNames.length === 1) {
|
|
53
|
+
var name = uniqueNames[0];
|
|
54
|
+
var n = counts[name];
|
|
55
|
+
if (allDone) {
|
|
56
|
+
switch (name) {
|
|
57
|
+
case "Read": return "Read " + n + " file" + (n > 1 ? "s" : "");
|
|
58
|
+
case "Edit": return "Edited " + n + " file" + (n > 1 ? "s" : "");
|
|
59
|
+
case "Write": return "Wrote " + n + " file" + (n > 1 ? "s" : "");
|
|
60
|
+
case "Bash": return "Ran " + n + " command" + (n > 1 ? "s" : "");
|
|
61
|
+
case "Grep": return "Searched " + n + " pattern" + (n > 1 ? "s" : "");
|
|
62
|
+
case "Glob": return "Found " + n + " pattern" + (n > 1 ? "s" : "");
|
|
63
|
+
case "Task": return "Ran " + n + " task" + (n > 1 ? "s" : "");
|
|
64
|
+
case "WebSearch": return "Searched " + n + " quer" + (n > 1 ? "ies" : "y");
|
|
65
|
+
case "WebFetch": return "Fetched " + n + " URL" + (n > 1 ? "s" : "");
|
|
66
|
+
default: return "Ran " + n + " tool" + (n > 1 ? "s" : "");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
switch (name) {
|
|
70
|
+
case "Read": return "Reading " + n + " file" + (n > 1 ? "s" : "") + "...";
|
|
71
|
+
case "Edit": return "Editing " + n + " file" + (n > 1 ? "s" : "") + "...";
|
|
72
|
+
case "Write": return "Writing " + n + " file" + (n > 1 ? "s" : "") + "...";
|
|
73
|
+
case "Bash": return "Running " + n + " command" + (n > 1 ? "s" : "") + "...";
|
|
74
|
+
case "Grep": return "Searching " + n + " pattern" + (n > 1 ? "s" : "") + "...";
|
|
75
|
+
case "Glob": return "Finding " + n + " pattern" + (n > 1 ? "s" : "") + "...";
|
|
76
|
+
case "Task": return "Running " + n + " task" + (n > 1 ? "s" : "") + "...";
|
|
77
|
+
case "WebSearch": return "Searching " + n + " quer" + (n > 1 ? "ies" : "y") + "...";
|
|
78
|
+
case "WebFetch": return "Fetching " + n + " URL" + (n > 1 ? "s" : "") + "...";
|
|
79
|
+
default: return "Running " + n + " tool" + (n > 1 ? "s" : "") + "...";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mixed tools
|
|
84
|
+
if (allDone) return "Ran " + count + " tools";
|
|
85
|
+
return "Running " + count + " tools...";
|
|
86
|
+
}
|
|
@@ -55,8 +55,9 @@ export function enhanceSettingsSelects(root) {
|
|
|
55
55
|
item.hidden = option.hidden;
|
|
56
56
|
item.onclick = function (event) {
|
|
57
57
|
event.preventDefault();
|
|
58
|
+
var changed = select.value !== option.value;
|
|
58
59
|
select.value = option.value;
|
|
59
|
-
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
60
|
+
if (changed) select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
60
61
|
sync();
|
|
61
62
|
close(true);
|
|
62
63
|
};
|
|
@@ -7,37 +7,53 @@ export function renderBrowserTabs(el, select, send) {
|
|
|
7
7
|
var selected = store.get('sharedBrowser');
|
|
8
8
|
var key = JSON.stringify([tabs.map(function (tab) { return [tab.id, tab.url, tab.control, tab.phase, tab.incognito, tab.personalCookiesAvailable]; }), selected && selected.id, store.get('connected')]);
|
|
9
9
|
if (bar.dataset.key === key) return;
|
|
10
|
-
bar.
|
|
10
|
+
var focused = bar.contains(document.activeElement) ? document.activeElement : null;
|
|
11
|
+
var focusedId = focused && focused.dataset.browserId, focusedAction = focused && focused.dataset.tabAction;
|
|
12
|
+
var previousSelection = bar.dataset.selected;
|
|
13
|
+
var previousStrip = bar.querySelector('.shared-browser-tab-list'), scrollLeft = previousStrip ? previousStrip.scrollLeft : 0;
|
|
14
|
+
bar.dataset.key = key; bar.dataset.selected = selected && selected.id || '';
|
|
11
15
|
bar.replaceChildren();
|
|
16
|
+
var strip = document.createElement('div'); strip.className = 'shared-browser-tab-list'; strip.setAttribute('role', 'presentation');
|
|
17
|
+
bar.appendChild(strip);
|
|
12
18
|
tabs.forEach(function (tab, index) {
|
|
13
|
-
var item = document.createElement('div'); item.className = 'shared-browser-tab';
|
|
19
|
+
var item = document.createElement('div'); item.className = 'shared-browser-tab'; item.setAttribute('role', 'presentation');
|
|
20
|
+
var active = !!selected && selected.id === tab.id;
|
|
14
21
|
var button = document.createElement('button'); button.type = 'button'; button.setAttribute('role', 'tab');
|
|
15
|
-
button.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
var
|
|
20
|
-
|
|
22
|
+
button.dataset.browserId = tab.id; button.dataset.tabAction = 'select';
|
|
23
|
+
button.setAttribute('aria-selected', String(active)); button.tabIndex = active ? 0 : -1;
|
|
24
|
+
var title = 'New tab';
|
|
25
|
+
try { if (tab.url) title = new URL(tab.url).hostname || 'New tab'; } catch (e) {}
|
|
26
|
+
var mode = tab.phase === 'ended' ? 'Ended' : tab.phase === 'starting' ? 'Starting' : tab.control === 'agent' ? 'You and Clay can control this tab' : 'Only you can control this tab. Clay is paused.';
|
|
27
|
+
var description = title + ' · ' + (tab.incognito === false ? 'Personal browser' : 'Incognito') + ' · ' + mode;
|
|
28
|
+
var label = document.createElement('span'); label.className = 'shared-browser-tab-label'; label.textContent = title;
|
|
29
|
+
button.innerHTML = iconHtml(tab.personalCookiesAvailable && tab.incognito ? 'glasses' : 'globe');
|
|
30
|
+
button.appendChild(label); button.setAttribute('aria-label', description); button.setAttribute('data-tip', description);
|
|
31
|
+
item.dataset.phase = tab.phase; item.dataset.control = tab.control;
|
|
21
32
|
button.onclick = function () { select(tab.id); };
|
|
22
33
|
button.onkeydown = function (event) {
|
|
23
34
|
var next = event.key === 'ArrowRight' ? (index + 1) % tabs.length : event.key === 'ArrowLeft' ? (index + tabs.length - 1) % tabs.length : event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : -1;
|
|
24
35
|
if (next < 0) return;
|
|
25
36
|
event.preventDefault(); select(tabs[next].id); bar.querySelectorAll('[role="tab"]')[next].focus();
|
|
26
37
|
};
|
|
27
|
-
var close = document.createElement('button'); close.type = 'button'; close.
|
|
38
|
+
var close = document.createElement('button'); close.type = 'button'; close.className = 'shared-browser-tab-close'; close.innerHTML = iconHtml('x');
|
|
39
|
+
close.dataset.browserId = tab.id; close.dataset.tabAction = 'close';
|
|
40
|
+
close.setAttribute('aria-label', 'Close browser tab ' + (index + 1)); close.setAttribute('data-tip', 'Close tab'); close.disabled = !store.get('connected');
|
|
28
41
|
close.onclick = function () { send('close', { browserId: tab.id }); };
|
|
29
|
-
item.append(button, close);
|
|
42
|
+
item.append(button, close); strip.appendChild(item);
|
|
30
43
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
var incognito = document.createElement('button'); incognito.type = 'button';
|
|
37
|
-
incognito.innerHTML = iconHtml('glasses');
|
|
38
|
-
incognito.setAttribute('aria-label', 'New incognito tab'); incognito.title = 'New incognito tab · Site data is not saved';
|
|
39
|
-
incognito.disabled = !store.get('connected');
|
|
40
|
-
incognito.onclick = function () { send('open', { newTab: true, incognito: true }); };
|
|
41
|
-
bar.appendChild(incognito); refreshIcons();
|
|
44
|
+
function addButton(icon, label, description, fields, action) {
|
|
45
|
+
var button = document.createElement('button'); button.type = 'button'; button.className = 'shared-browser-new-tab';
|
|
46
|
+
button.innerHTML = iconHtml(icon); button.dataset.tabAction = action;
|
|
47
|
+
button.setAttribute('aria-label', label); button.setAttribute('data-tip', description); button.disabled = !store.get('connected');
|
|
48
|
+
button.onclick = function () { send('open', fields); }; bar.appendChild(button);
|
|
42
49
|
}
|
|
50
|
+
addButton('plus', 'New browser tab', selected && selected.personalCookiesAvailable ? 'New tab · Personal browser' : 'New incognito tab', { newTab: true }, 'new');
|
|
51
|
+
if (selected && selected.personalCookiesAvailable) addButton('glasses', 'New incognito tab', 'New incognito tab · Site data is not saved', { newTab: true, incognito: true }, 'incognito');
|
|
52
|
+
refreshIcons();
|
|
53
|
+
strip.scrollLeft = scrollLeft;
|
|
54
|
+
var buttons = Array.from(bar.querySelectorAll('button'));
|
|
55
|
+
var replacement = focused && buttons.find(function (button) { return button.dataset.browserId === focusedId && button.dataset.tabAction === focusedAction; });
|
|
56
|
+
if (focused) (replacement || bar.querySelector('[aria-selected="true"]') || buttons[0]).focus({ preventScroll: true });
|
|
57
|
+
var current = bar.querySelector('[aria-selected="true"]');
|
|
58
|
+
if (current && previousSelection !== bar.dataset.selected) current.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
43
59
|
}
|
|
@@ -15,7 +15,7 @@ import { openAddProjectModal } from './project-start-dialog.js';
|
|
|
15
15
|
import { showHomeHub } from './app-home-hub.js';
|
|
16
16
|
import { getPendingTuiAttention } from './app-notifications.js';
|
|
17
17
|
import { isExternalWorktree, externalWorktreeTooltip, appendExternalWorktreeBadge } from './worktree-location.js';
|
|
18
|
-
import { groupProjects } from './project-grouping.js';
|
|
18
|
+
import { groupProjects, getProjectAbbrev } from './project-grouping.js';
|
|
19
19
|
import { showProjectAccessPopover } from './project-access.js';
|
|
20
20
|
import { renderProjectMates, mateForWorkspace } from './project-mate-navigation.js';
|
|
21
21
|
import { openProjectCreateChoice } from './project-mate-creation-workspace.js';
|
|
@@ -337,15 +337,6 @@ export function initSidebarProjects() {
|
|
|
337
337
|
export function getCachedProjectList() { return cachedProjectList; }
|
|
338
338
|
export function getCachedCurrentSlug() { return cachedCurrentSlug; }
|
|
339
339
|
|
|
340
|
-
function getProjectAbbrev(name) {
|
|
341
|
-
if (!name) return "?";
|
|
342
|
-
var words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
|
|
343
|
-
if (words.length >= 2) {
|
|
344
|
-
return (words[0][0] + words[1][0]).toUpperCase();
|
|
345
|
-
}
|
|
346
|
-
return name.substring(0, 2).toUpperCase();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
340
|
export { getProjectAbbrev };
|
|
350
341
|
|
|
351
342
|
// --- Project context menu ---
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { currentLayoutScope } from './split-view-layout-scope.js';
|
|
3
|
+
|
|
4
|
+
export function clearEmptySplit(host) {
|
|
5
|
+
var pane = host && host.querySelector(':scope > .split-session-empty-pane');
|
|
6
|
+
var menu = document.getElementById('split-view-create-menu');
|
|
7
|
+
if (pane && menu && pane.contains(menu)) {
|
|
8
|
+
menu.hidden = true;
|
|
9
|
+
if (menu.contains(document.activeElement)) document.activeElement.blur();
|
|
10
|
+
if (pane.contains(menu)) document.body.appendChild(menu);
|
|
11
|
+
}
|
|
12
|
+
if (pane) pane.remove();
|
|
13
|
+
var divider = host && host.querySelector(':scope > .split-session-reserved-divider');
|
|
14
|
+
if (divider) divider.remove();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function reserveEmptySplit(host, geometry, inset) {
|
|
18
|
+
var state = store.get('splitSessionPicker');
|
|
19
|
+
var receipt = state && state.receipt;
|
|
20
|
+
var target = receipt && geometry.panes[receipt.origin];
|
|
21
|
+
if (!state || !state.side || !target || !currentLayoutScope(receipt.scope)) { clearEmptySplit(host); return false; }
|
|
22
|
+
var side = state.side, vertical = side === 'top' || side === 'bottom', before = side === 'left' || side === 'top';
|
|
23
|
+
var size = vertical ? 'height' : 'width', axis = vertical ? 'y' : 'x';
|
|
24
|
+
var half = Math.max(0, (target[size] - 7) / 2);
|
|
25
|
+
var original = Object.assign({}, target), empty = Object.assign({}, target), gap = Object.assign({}, target);
|
|
26
|
+
original[size] = half; empty[size] = half; gap[size] = 7;
|
|
27
|
+
gap[axis] += half;
|
|
28
|
+
if (before) original[axis] += half + 7; else empty[axis] += half + 7;
|
|
29
|
+
geometry.panes[receipt.origin] = original;
|
|
30
|
+
var pane = host.querySelector(':scope > .split-session-empty-pane');
|
|
31
|
+
if (!pane) {
|
|
32
|
+
pane = document.createElement('section'); pane.className = 'split-session-empty-pane';
|
|
33
|
+
pane.innerHTML = '<div class="split-session-empty-body"></div>';
|
|
34
|
+
host.appendChild(pane);
|
|
35
|
+
}
|
|
36
|
+
pane.dataset.side = side; pane.setAttribute('aria-label', 'Empty ' + side + ' split');
|
|
37
|
+
var menu = document.getElementById('split-view-create-menu');
|
|
38
|
+
var body = pane.querySelector('.split-session-empty-body');
|
|
39
|
+
if (menu && menu.parentNode !== body) body.appendChild(menu);
|
|
40
|
+
var divider = host.querySelector(':scope > .split-session-reserved-divider');
|
|
41
|
+
if (!divider) { divider = document.createElement('div'); divider.className = 'split-session-reserved-divider'; divider.setAttribute('aria-hidden', 'true'); host.appendChild(divider); }
|
|
42
|
+
[[pane, empty], [divider, gap]].forEach(function (entry) {
|
|
43
|
+
entry[0].style.left = inset.left + entry[1].x + 'px'; entry[0].style.top = inset.top + entry[1].y + 'px';
|
|
44
|
+
entry[0].style.width = entry[1].width + 'px'; entry[0].style.height = entry[1].height + 'px';
|
|
45
|
+
});
|
|
46
|
+
return true;
|
|
47
|
+
}
|