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
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
import { store } from './store.js';
|
|
17
17
|
import { refreshIcons } from './icons.js';
|
|
18
18
|
import { getCachedProjects, switchProject } from './app-projects.js';
|
|
19
|
-
import {
|
|
20
|
-
import { openHomeChat } from './home-mate-chat.js';
|
|
19
|
+
import { openMateWorkspace, mateForWorkspace } from './project-mate-navigation.js';
|
|
21
20
|
import { mateAvatarUrl } from './avatar.js';
|
|
22
21
|
import { parseEmojis } from './markdown.js';
|
|
23
22
|
import { isExternalWorktree, externalWorktreeTooltip, appendExternalWorktreeBadge } from './worktree-location.js';
|
|
@@ -34,7 +33,6 @@ var _entries = [];
|
|
|
34
33
|
|
|
35
34
|
export function openSwitcherForMode(mode) {
|
|
36
35
|
if (mode !== 'project' && mode !== 'mate') return;
|
|
37
|
-
if (mode === 'mate' && !isHomeHubVisible()) return;
|
|
38
36
|
if (_open) closeSwitcher(false);
|
|
39
37
|
openSwitcher(mode);
|
|
40
38
|
}
|
|
@@ -145,7 +143,6 @@ function handleKeyDown(e) {
|
|
|
145
143
|
// same shortcut cycles.
|
|
146
144
|
if (isMod && (k === 'e' || k === 'm')) {
|
|
147
145
|
var requestedMode = k === 'e' ? 'project' : 'mate';
|
|
148
|
-
if (requestedMode === 'mate' && !isHomeHubVisible()) return;
|
|
149
146
|
e.preventDefault();
|
|
150
147
|
e.stopPropagation();
|
|
151
148
|
if (!_open) {
|
|
@@ -246,7 +243,8 @@ function pickInitialIndex() {
|
|
|
246
243
|
|
|
247
244
|
function currentActiveKey() {
|
|
248
245
|
if (_mode === 'mate') {
|
|
249
|
-
|
|
246
|
+
var mate = mateForWorkspace(store.snap());
|
|
247
|
+
return mate ? mate.id : null;
|
|
250
248
|
}
|
|
251
249
|
return store.get('currentSlug');
|
|
252
250
|
}
|
|
@@ -277,7 +275,8 @@ function buildProjectEntries() {
|
|
|
277
275
|
|
|
278
276
|
function buildMateEntries() {
|
|
279
277
|
var mates = store.get('cachedMatesList') || [];
|
|
280
|
-
var
|
|
278
|
+
var current = mateForWorkspace(store.snap());
|
|
279
|
+
var currentMateId = current ? current.id : null;
|
|
281
280
|
// Natural list order; MRU is only for choosing the initial highlight.
|
|
282
281
|
return mates.map(function (m) {
|
|
283
282
|
return {
|
|
@@ -302,8 +301,7 @@ function closeSwitcher(commit) {
|
|
|
302
301
|
if (!entry || entry.disabled) return;
|
|
303
302
|
if (_mode === 'mate') {
|
|
304
303
|
if (entry.key !== currentActiveKey()) {
|
|
305
|
-
|
|
306
|
-
openHomeChat(entry.key);
|
|
304
|
+
openMateWorkspace(entry.key);
|
|
307
305
|
}
|
|
308
306
|
} else {
|
|
309
307
|
if (entry.key !== store.get('currentSlug')) switchProject(entry.key);
|
|
@@ -18,12 +18,12 @@ function setupAction(mode, icon, title, text, disabled) {
|
|
|
18
18
|
|
|
19
19
|
export function projectsHubContent(state) {
|
|
20
20
|
var projects = ordinaryProjects(state.projectsHubList);
|
|
21
|
-
var ready = state.projectListLoaded
|
|
21
|
+
var ready = state.projectListLoaded;
|
|
22
22
|
var welcome = ready && (state.projectsShowGuide || (!projects.length && (state.onboardingStep || 'welcome') === 'welcome'));
|
|
23
23
|
var allowed = state.projectsAccessLoaded && (!state.permissions || state.permissions.createProject === true);
|
|
24
24
|
var available = allowed && state.connected && !state.pendingHomeProjectSlug;
|
|
25
|
-
var html = '<header class="projects-header"><a class="projects-brand" href="/
|
|
26
|
-
'<nav aria-label="Workspace navigation">' + button('
|
|
25
|
+
var html = '<header class="projects-header"><a class="projects-brand" href="/projects"><img src="/clay-studio-symbol.png" alt="">Clay Studio</a>' +
|
|
26
|
+
'<nav aria-label="Workspace navigation">' + button('settings', 'Settings', 'projects-button-quiet') + '</nav></header>';
|
|
27
27
|
html += '<main class="projects-main" aria-labelledby="projects-title">';
|
|
28
28
|
if (!ready) return html + '<div class="projects-loading" role="status"><h1 id="projects-title" tabindex="-1">Your projects</h1><p>Loading your workspace…</p></div></main>';
|
|
29
29
|
if (welcome) {
|
|
@@ -31,9 +31,8 @@ export function projectsHubContent(state) {
|
|
|
31
31
|
'<h1 id="projects-title" tabindex="-1">A place for<br>your next idea.</h1>' +
|
|
32
32
|
'<p class="projects-lead">Bring a project. Describe what you want to build.<br>Your AI Driver helps you take it from there.</p>' +
|
|
33
33
|
'<div class="projects-intro-grid"><div><span>01 / PROJECTS</span><h2>Work on something real.</h2><p>Open a folder, start fresh, or clone a repository. Your conversations and tools stay with the project.</p></div>' +
|
|
34
|
-
'<div><span>02 /
|
|
35
|
-
'<div class="projects-welcome-actions">' + button('start', projects.length ? 'Explore your projects ' + iconHtml('arrow-right') : 'Set up your first project ' + iconHtml('arrow-right'), 'projects-button-primary') +
|
|
36
|
-
button('skip', 'Explore Home first', 'projects-button-quiet') + '</div>' +
|
|
34
|
+
'<div><span>02 / MATES</span><h2>Bring in the right perspective.</h2><p>Your Mates live beside projects and open their conversations in the same focused workspace.</p></div></div>' +
|
|
35
|
+
'<div class="projects-welcome-actions">' + button('start', projects.length ? 'Explore your projects ' + iconHtml('arrow-right') : 'Set up your first project ' + iconHtml('arrow-right'), 'projects-button-primary') + '</div>' +
|
|
37
36
|
'<p class="projects-footnote">You can revisit this guide from Projects at any time.</p></section></main>';
|
|
38
37
|
}
|
|
39
38
|
html += '<div class="projects-heading"><div><div class="projects-eyebrow">YOUR WORKSPACE</div><h1 id="projects-title" tabindex="-1">' + (projects.length ? 'Projects' : 'Start with a project.') + '</h1>' +
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
-
import { hideHomeHub, showHomeHub } from './app-home-hub.js';
|
|
4
3
|
import { openAddProjectModal, switchProject } from './app-projects.js';
|
|
5
4
|
import { openUserSettings } from './user-settings.js';
|
|
6
5
|
import { rememberHomePrimarySurface, updateHomeSurfacePreference } from './home-surface.js';
|
|
@@ -37,7 +36,6 @@ function focusHeading() {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
export function showProjectsHub(fromHistory) {
|
|
40
|
-
hideHomeHub();
|
|
41
39
|
store.set({ projectsHubVisible: true, pendingHomeProjectSlug: null });
|
|
42
40
|
document.body.classList.add('projects-active');
|
|
43
41
|
root().classList.remove('hidden');
|
|
@@ -67,14 +65,12 @@ function handleClick(event) {
|
|
|
67
65
|
if (!button || button.disabled) return;
|
|
68
66
|
event.preventDefault();
|
|
69
67
|
var action = button.dataset.projectsAction;
|
|
70
|
-
if (action === 'home') { showHomeHub(); return; }
|
|
71
68
|
if (action === 'settings') { openUserSettings(); return; }
|
|
72
69
|
if (action === 'guide') { store.set({ projectsShowGuide: true }); render(); focusHeading(); return; }
|
|
73
|
-
if (action === 'start'
|
|
70
|
+
if (action === 'start') {
|
|
74
71
|
store.set({ projectsShowGuide: false });
|
|
75
72
|
updateHomeSurfacePreference({ onboardingStep: 'project' });
|
|
76
|
-
|
|
77
|
-
else { render(); focusHeading(); }
|
|
73
|
+
render(); focusHeading();
|
|
78
74
|
return;
|
|
79
75
|
}
|
|
80
76
|
var ws = getWs();
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { copyToClipboard } from './utils.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
var activeShareUrl = null;
|
|
4
|
+
var activeShareTitle = null;
|
|
5
|
+
|
|
6
|
+
export function getShareUrl(projectSlug) {
|
|
7
|
+
var locationUrl = new URL(window.location.href);
|
|
8
|
+
if (projectSlug) {
|
|
9
|
+
var projectIndex = locationUrl.pathname.indexOf("/p/");
|
|
10
|
+
var prefix = projectIndex === -1 ? "" : locationUrl.pathname.substring(0, projectIndex);
|
|
11
|
+
locationUrl.pathname = prefix + "/p/" + encodeURIComponent(projectSlug) + "/";
|
|
12
|
+
locationUrl.search = "";
|
|
13
|
+
locationUrl.hash = "";
|
|
14
|
+
}
|
|
15
|
+
var url = locationUrl.href;
|
|
5
16
|
var h = window.location.hostname;
|
|
6
17
|
if ((h === "localhost" || h === "127.0.0.1") && window.__lanHost) {
|
|
7
18
|
url = url.replace(h + ":" + window.location.port, window.__lanHost);
|
|
@@ -9,13 +20,15 @@ function getShareUrl() {
|
|
|
9
20
|
return url;
|
|
10
21
|
}
|
|
11
22
|
|
|
12
|
-
export function triggerShare() {
|
|
13
|
-
|
|
23
|
+
export function triggerShare(options) {
|
|
24
|
+
options = options || {};
|
|
25
|
+
var url = getShareUrl(options.projectSlug);
|
|
26
|
+
var title = options.title || document.title || "Clay";
|
|
14
27
|
|
|
15
28
|
// Use Web Share API on mobile only
|
|
16
29
|
var isMobile = window.innerWidth <= 768 || /Mobi|Android/i.test(navigator.userAgent);
|
|
17
30
|
if (isMobile && navigator.share) {
|
|
18
|
-
navigator.share({ title:
|
|
31
|
+
navigator.share({ title: title, url: url }).catch(function () {});
|
|
19
32
|
return;
|
|
20
33
|
}
|
|
21
34
|
|
|
@@ -28,6 +41,8 @@ export function triggerShare() {
|
|
|
28
41
|
var qr = qrcode(0, "M");
|
|
29
42
|
qr.addData(url);
|
|
30
43
|
qr.make();
|
|
44
|
+
activeShareUrl = url;
|
|
45
|
+
activeShareTitle = title;
|
|
31
46
|
qrCanvas.innerHTML = qr.createSvgTag(5, 0);
|
|
32
47
|
qrUrl.innerHTML = url + '<span class="qr-hint">click to copy</span>';
|
|
33
48
|
|
|
@@ -49,7 +64,7 @@ export function initQrCode() {
|
|
|
49
64
|
|
|
50
65
|
// click URL to copy
|
|
51
66
|
qrUrl.addEventListener("click", function () {
|
|
52
|
-
var url = getShareUrl();
|
|
67
|
+
var url = activeShareUrl || getShareUrl();
|
|
53
68
|
copyToClipboard(url).then(function () {
|
|
54
69
|
qrUrl.innerHTML = "Copied!";
|
|
55
70
|
qrUrl.classList.add("copied");
|
|
@@ -62,6 +77,8 @@ export function initQrCode() {
|
|
|
62
77
|
|
|
63
78
|
qrOverlay.addEventListener("click", function () {
|
|
64
79
|
qrOverlay.classList.add("hidden");
|
|
80
|
+
activeShareUrl = null;
|
|
81
|
+
activeShareTitle = null;
|
|
65
82
|
});
|
|
66
83
|
|
|
67
84
|
// prevent closing when clicking the inner card
|
|
@@ -73,8 +90,8 @@ export function initQrCode() {
|
|
|
73
90
|
var qrShareBtn = document.getElementById("qr-share-btn");
|
|
74
91
|
if (qrShareBtn) {
|
|
75
92
|
qrShareBtn.addEventListener("click", function () {
|
|
76
|
-
var url = getShareUrl();
|
|
77
|
-
navigator.share({ title: document.title || "Clay", url: url }).catch(function () {});
|
|
93
|
+
var url = activeShareUrl || getShareUrl();
|
|
94
|
+
navigator.share({ title: activeShareTitle || document.title || "Clay", url: url }).catch(function () {});
|
|
78
95
|
});
|
|
79
96
|
}
|
|
80
97
|
|
|
@@ -82,6 +99,8 @@ export function initQrCode() {
|
|
|
82
99
|
document.addEventListener("keydown", function (e) {
|
|
83
100
|
if (e.key === "Escape" && !qrOverlay.classList.contains("hidden")) {
|
|
84
101
|
qrOverlay.classList.add("hidden");
|
|
102
|
+
activeShareUrl = null;
|
|
103
|
+
activeShareTitle = null;
|
|
85
104
|
}
|
|
86
105
|
});
|
|
87
106
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Compact Clay conversation hosted inside the global search drawer.
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
-
import {
|
|
4
|
-
import { openHomeConversation } from './home-mate-chat.js';
|
|
3
|
+
import { openProjectSession } from './project-session-navigation.js';
|
|
5
4
|
import { refreshIcons } from './icons.js';
|
|
6
5
|
import { store } from './store.js';
|
|
7
6
|
import { mateAvatarUrl } from './avatar.js';
|
|
@@ -231,12 +230,9 @@ function respondToPermission(permission, decision) {
|
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
function expand() {
|
|
234
|
-
if (!state || !state.mateId || !state.
|
|
235
|
-
var mateId = state.mateId;
|
|
236
|
-
var sessionId = state.sessionId;
|
|
233
|
+
if (!state || !state.mateId || !state.localId) return;
|
|
237
234
|
if (closeHandler) closeHandler();
|
|
238
|
-
|
|
239
|
-
openHomeConversation(mateId, sessionId);
|
|
235
|
+
openProjectSession('mate-' + state.mateId, state.localId);
|
|
240
236
|
}
|
|
241
237
|
|
|
242
238
|
function render() {
|
|
@@ -259,7 +255,7 @@ function render() {
|
|
|
259
255
|
var actions = document.createElement("div");
|
|
260
256
|
actions.className = "search-clay-header-actions";
|
|
261
257
|
actions.appendChild(button("search", "Search", "Back to search", function () { detachSearchClayChat(); if (backHandler) backHandler(); }));
|
|
262
|
-
actions.appendChild(button("maximize-2", "Expand", "Open this conversation in
|
|
258
|
+
actions.appendChild(button("maximize-2", "Expand", "Open this conversation in project chat", expand));
|
|
263
259
|
header.appendChild(actions);
|
|
264
260
|
shell.appendChild(header);
|
|
265
261
|
transcript = document.createElement("div");
|
|
@@ -318,8 +314,8 @@ function render() {
|
|
|
318
314
|
sendButton.disabled = state.processing ? (!state.sessionId || state.stopping) : (!state.sessionId || !input.value.trim());
|
|
319
315
|
sendButton.setAttribute("aria-label", state.processing ? (state.stopping ? "Stopping Clay" : "Stop Clay") : "Send to Clay");
|
|
320
316
|
sendButton.innerHTML = state.processing ? '<i data-lucide="square" aria-hidden="true"></i>' : '<i data-lucide="arrow-up" aria-hidden="true"></i>';
|
|
321
|
-
var expandButton = shell.querySelector('.search-clay-icon-button[aria-label="Open this conversation in
|
|
322
|
-
if (expandButton) expandButton.disabled = !state.
|
|
317
|
+
var expandButton = shell.querySelector('.search-clay-icon-button[aria-label="Open this conversation in project chat"]');
|
|
318
|
+
if (expandButton) expandButton.disabled = !state.localId;
|
|
323
319
|
refreshIcons();
|
|
324
320
|
transcript.scrollTop = follow ? transcript.scrollHeight : previousTop;
|
|
325
321
|
}
|
|
@@ -331,7 +327,7 @@ export function startSearchClayChat(query, host, onBack, onClose) {
|
|
|
331
327
|
backHandler = onBack;
|
|
332
328
|
closeHandler = onClose;
|
|
333
329
|
var startedAt = Date.now();
|
|
334
|
-
state = { requestId: requestId(), mateId: null, sessionId: null, messages: [{ role: "user", text: clean }], queuedUsers: [], pendingTurns: 1, activities: [], permissions: [], activityExpanded: false, stream: "", draft: "", processing: true, stopping: false, awaitingCompletion: true, activity: "thinking", step: 0, startedAt: startedAt, updatedAt: startedAt, query: clean };
|
|
330
|
+
state = { requestId: requestId(), mateId: null, sessionId: null, localId: null, messages: [{ role: "user", text: clean }], queuedUsers: [], pendingTurns: 1, activities: [], permissions: [], activityExpanded: false, stream: "", draft: "", processing: true, stopping: false, awaitingCompletion: true, activity: "thinking", step: 0, startedAt: startedAt, updatedAt: startedAt, query: clean };
|
|
335
331
|
store.set({ searchClayChatState: state });
|
|
336
332
|
startProgressTimer();
|
|
337
333
|
render();
|
|
@@ -382,6 +378,7 @@ export function handleSearchClayMessage(msg) {
|
|
|
382
378
|
updateActivity(msg);
|
|
383
379
|
} else if (msg.type === "home_mate_history") {
|
|
384
380
|
state.sessionId = msg.sessionId || state.sessionId;
|
|
381
|
+
state.localId = typeof msg.localId === "number" ? msg.localId : state.localId;
|
|
385
382
|
state.messages = Array.isArray(msg.messages) ? msg.messages.filter(function (item) { return item && (item.role === "user" || item.role === "assistant"); }).map(function (item) { return { role: item.role, text: item.text || "" }; }) : state.messages;
|
|
386
383
|
state.processing = msg.isProcessing === true;
|
|
387
384
|
state.awaitingCompletion = state.processing;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function formatSessionActivity(value) {
|
|
2
|
+
if (!value) return "";
|
|
3
|
+
var date = new Date(value);
|
|
4
|
+
if (isNaN(date.getTime())) return "";
|
|
5
|
+
try {
|
|
6
|
+
return "Last activity: " + new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(date);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
return "Last activity: " + date.toLocaleString();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { formatSessionActivity };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The inline "new session" draft row that sits under a folder header (also when
|
|
2
|
-
// the folder is collapsed): provider icon, a compact provider picker
|
|
3
|
-
// Cancel. The
|
|
2
|
+
// the folder is collapsed): provider icon, a compact provider picker, Create
|
|
3
|
+
// and icon Cancel. The header action remains New session. State and server round
|
|
4
4
|
// trips are in session-create.js; this module only builds and repaints the DOM
|
|
5
5
|
// from the store, so the row keeps its provider, focus and error across list
|
|
6
6
|
// rerenders and server updates.
|
|
@@ -12,7 +12,7 @@ import { startNewSession } from './sidebar-sessions.js';
|
|
|
12
12
|
import { closeMobileSheet } from './sidebar-mobile.js';
|
|
13
13
|
import {
|
|
14
14
|
createState, createOptions, installedVendorIds,
|
|
15
|
-
|
|
15
|
+
beginSessionCreate, closeSessionCreate, chooseVendor, setCreateMenu, submitSessionCreate, submitMateSessionCreate, saveProjectDefault, retryOptions
|
|
16
16
|
} from './session-create.js';
|
|
17
17
|
|
|
18
18
|
// Every registered provider, in the established order, followed by any the
|
|
@@ -61,6 +61,15 @@ function focusHeaderButton(folderId) {
|
|
|
61
61
|
requestAnimationFrame(applyFocus);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function focusCreateRow(folderId) {
|
|
65
|
+
var rows = document.querySelectorAll('.session-create-row[data-folder-id="' + folderId + '"]');
|
|
66
|
+
for (var i = 0; i < rows.length; i++) {
|
|
67
|
+
if (!rows[i].getClientRects().length) continue;
|
|
68
|
+
var target = rows[i].querySelector('[data-focus-key="vendor"]') || rows[i].querySelector('[data-focus-key="create"]') || rows[i].querySelector('[data-focus-key="cancel"]');
|
|
69
|
+
if (target && !target.disabled) { target.focus({ preventScroll: true }); return; }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
function prefersReducedMotion() {
|
|
65
74
|
return typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
66
75
|
}
|
|
@@ -106,20 +115,25 @@ function createStatusMessage(form, options, valid) {
|
|
|
106
115
|
function paintNewSessionButton(btn, folderId, label) {
|
|
107
116
|
var form = createState();
|
|
108
117
|
var active = !!(form && form.folderId === folderId);
|
|
118
|
+
var direct = active && form.mode === "mate-direct";
|
|
109
119
|
var options = createOptions();
|
|
110
|
-
var valid = active && options.loaded && installedVendorIds().indexOf(form.vendor) !== -1;
|
|
120
|
+
var valid = direct || (active && options.loaded && installedVendorIds().indexOf(form.vendor) !== -1);
|
|
111
121
|
var pending = active && form.phase === "pending";
|
|
112
|
-
var
|
|
113
|
-
var
|
|
122
|
+
var uncertain = direct && form.phase === "uncertain";
|
|
123
|
+
var buttonText = direct ? (pending ? "Creating…" : (uncertain ? "Check sessions" : "Retry")) : "New session";
|
|
124
|
+
var status = active ? (direct ? (form.error || (pending ? "Creating session…" : "")) : createStatusMessage(form, options, valid)) : "";
|
|
114
125
|
ensureNewSessionButtonContent(btn);
|
|
115
|
-
btn.classList.toggle("is-create",
|
|
126
|
+
btn.classList.toggle("is-create", direct);
|
|
116
127
|
btn.setAttribute("aria-expanded", String(active));
|
|
117
128
|
btn.setAttribute("aria-busy", String(pending));
|
|
118
|
-
|
|
129
|
+
var activeLabel = direct
|
|
130
|
+
? (pending ? "Creating session in " : (uncertain ? "Check sessions in " : "Retry new session in "))
|
|
131
|
+
: "New session in ";
|
|
132
|
+
btn.setAttribute("aria-label", active ? activeLabel + label : "New session in " + label);
|
|
119
133
|
var controls = btn.getAttribute("aria-controls");
|
|
120
134
|
if (active && status && controls) btn.setAttribute("aria-describedby", controls + "-status");
|
|
121
135
|
else btn.removeAttribute("aria-describedby");
|
|
122
|
-
btn.disabled =
|
|
136
|
+
btn.disabled = direct ? (pending || uncertain) : !!(form && form.phase === "pending");
|
|
123
137
|
setNewSessionButtonText(btn, buttonText, active && (!form.openedAt || Date.now() - form.openedAt < 400 || btn.isConnected));
|
|
124
138
|
refreshIcons();
|
|
125
139
|
}
|
|
@@ -136,8 +150,12 @@ export function renderNewSessionButton(folderId, label, surface) {
|
|
|
136
150
|
btn.addEventListener("click", function (event) {
|
|
137
151
|
event.stopPropagation();
|
|
138
152
|
var live = createState();
|
|
139
|
-
if (live && live.folderId === folderId) {
|
|
140
|
-
|
|
153
|
+
if (live && live.folderId === folderId) {
|
|
154
|
+
if (live.mode === "mate-direct") submitMateSessionCreate();
|
|
155
|
+
else focusCreateRow(folderId);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
beginSessionCreate(folderId);
|
|
141
159
|
focusHeaderButton(folderId);
|
|
142
160
|
});
|
|
143
161
|
return btn;
|
|
@@ -160,6 +178,10 @@ export function renderCreateRow(folderId, label, surface) {
|
|
|
160
178
|
root.setAttribute("role", "group");
|
|
161
179
|
root.setAttribute("aria-label", "New session in " + label);
|
|
162
180
|
root.dataset.folderId = folderId;
|
|
181
|
+
if (form.mode === "mate-direct") {
|
|
182
|
+
paintMateDirectRow(root, form);
|
|
183
|
+
return root;
|
|
184
|
+
}
|
|
163
185
|
root.addEventListener("keydown", function (event) {
|
|
164
186
|
// Enter and Escape belong to the input method while composing.
|
|
165
187
|
if (event.isComposing || event.keyCode === 229) return;
|
|
@@ -195,6 +217,43 @@ export function renderCreateRow(folderId, label, surface) {
|
|
|
195
217
|
return root;
|
|
196
218
|
}
|
|
197
219
|
|
|
220
|
+
function paintMateDirectRow(root, form) {
|
|
221
|
+
var pending = form.phase === "pending";
|
|
222
|
+
var uncertain = form.phase === "uncertain";
|
|
223
|
+
root.setAttribute("aria-busy", String(pending));
|
|
224
|
+
root.innerHTML = "";
|
|
225
|
+
var line = document.createElement("div");
|
|
226
|
+
line.className = "session-create-line session-create-direct-line";
|
|
227
|
+
var status = document.createElement("div");
|
|
228
|
+
status.id = root.id + "-status";
|
|
229
|
+
status.className = "session-create-status" + (form.error ? " is-error" : "");
|
|
230
|
+
status.setAttribute("role", form.error ? "alert" : "status");
|
|
231
|
+
status.textContent = form.error || "Creating session…";
|
|
232
|
+
line.appendChild(status);
|
|
233
|
+
if (!pending && !uncertain) {
|
|
234
|
+
var retry = document.createElement("button");
|
|
235
|
+
retry.type = "button";
|
|
236
|
+
retry.className = "session-create-retry";
|
|
237
|
+
retry.dataset.focusKey = "retry";
|
|
238
|
+
retry.textContent = "Retry";
|
|
239
|
+
retry.addEventListener("click", function () { submitMateSessionCreate(); });
|
|
240
|
+
line.appendChild(retry);
|
|
241
|
+
}
|
|
242
|
+
var cancel = iconButton("x", "Close new session status", "cancel", function () { closeAndFocus(form.folderId); });
|
|
243
|
+
cancel.disabled = pending;
|
|
244
|
+
line.appendChild(cancel);
|
|
245
|
+
root.appendChild(line);
|
|
246
|
+
if (form.wantFocus && !pending && !uncertain) {
|
|
247
|
+
queueMicrotask(function () {
|
|
248
|
+
if (!retry.isConnected) return;
|
|
249
|
+
retry.focus({ preventScroll: true });
|
|
250
|
+
var live = createState();
|
|
251
|
+
if (live) store.set({ sessionCreate: Object.assign({}, live, { wantFocus: false }) });
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
refreshIcons();
|
|
255
|
+
}
|
|
256
|
+
|
|
198
257
|
function closeMenu(refocus) {
|
|
199
258
|
var live = createState();
|
|
200
259
|
if (!live || !live.menuOpen) return;
|
|
@@ -223,10 +282,16 @@ function submitAndClose() {
|
|
|
223
282
|
function paintRow(root) {
|
|
224
283
|
var form = createState();
|
|
225
284
|
if (!form) return;
|
|
285
|
+
if (form.mode === "mate-direct") {
|
|
286
|
+
paintMateDirectRow(root, form);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
226
289
|
var options = createOptions();
|
|
227
290
|
var pending = form.phase === "pending";
|
|
228
291
|
var active = document.activeElement;
|
|
229
292
|
var focusKey = active && root.contains(active) ? active.dataset.focusKey : null;
|
|
293
|
+
var installed = installedVendorIds();
|
|
294
|
+
var valid = options.loaded && installed.indexOf(form.vendor) !== -1;
|
|
230
295
|
// An explicit focus request wins over wherever focus happens to be.
|
|
231
296
|
if (form.wantFocus && root.getClientRects().length) focusKey = form.focusKey || "vendor";
|
|
232
297
|
root.setAttribute("aria-busy", String(pending));
|
|
@@ -234,8 +299,6 @@ function paintRow(root) {
|
|
|
234
299
|
|
|
235
300
|
var line = document.createElement("div");
|
|
236
301
|
line.className = "session-create-line";
|
|
237
|
-
var installed = installedVendorIds();
|
|
238
|
-
|
|
239
302
|
var icon = document.createElement("img");
|
|
240
303
|
icon.className = "session-create-icon";
|
|
241
304
|
icon.alt = "";
|
|
@@ -268,12 +331,21 @@ function paintRow(root) {
|
|
|
268
331
|
});
|
|
269
332
|
line.appendChild(picker);
|
|
270
333
|
|
|
334
|
+
var submit = document.createElement("button");
|
|
335
|
+
submit.type = "button";
|
|
336
|
+
submit.className = "session-create-submit";
|
|
337
|
+
submit.dataset.focusKey = "create";
|
|
338
|
+
submit.textContent = "Create";
|
|
339
|
+
submit.setAttribute("aria-label", "Create " + (root.getAttribute("aria-label") || "new session"));
|
|
340
|
+
submit.disabled = pending || !valid;
|
|
341
|
+
submit.addEventListener("click", function (event) { event.stopPropagation(); submitAndClose(); });
|
|
342
|
+
line.appendChild(submit);
|
|
343
|
+
|
|
271
344
|
var cancel = iconButton("x", "Cancel new session", "cancel", function () { closeAndFocus(form.folderId); });
|
|
272
345
|
cancel.disabled = pending;
|
|
273
346
|
line.appendChild(cancel);
|
|
274
347
|
root.appendChild(line);
|
|
275
348
|
|
|
276
|
-
var valid = options.loaded && installed.indexOf(form.vendor) !== -1;
|
|
277
349
|
var optionsError = options.error || "";
|
|
278
350
|
var message = createStatusMessage(form, options, valid);
|
|
279
351
|
var status = document.createElement("div");
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Every real folder header (custom folders and Unfiled, not the Favorites tag) has a quiet
|
|
4
4
|
// "+ New session" button that opens one draft row directly under that header:
|
|
5
|
-
// provider icon, a compact provider picker and icon Cancel
|
|
6
|
-
// action that opened the draft becomes Create. Only the
|
|
5
|
+
// provider icon, a compact provider picker, Create and icon Cancel. Only the
|
|
7
6
|
// provider is chosen; the model and effort stay whatever that provider's
|
|
8
7
|
// existing defaults produce. The installed providers and the project default
|
|
9
8
|
// come from the server; the saved project default is preselected, and a
|
|
10
9
|
// default changes only through the explicit action beside an available
|
|
11
10
|
// provider. All mutable state lives in the store (sessionCreate,
|
|
12
11
|
// sessionCreateOptions, sessionCreateLocks, sessionCreateReveal); the DOM is
|
|
13
|
-
// built in session-create-form.js.
|
|
12
|
+
// built in session-create-form.js. Provider-mode submission is an explicit
|
|
13
|
+
// Create action inside the draft row; the header only opens or focuses it.
|
|
14
14
|
|
|
15
15
|
import { store } from './store.js';
|
|
16
16
|
import { getWs } from './ws-ref.js';
|
|
@@ -18,6 +18,7 @@ import { showToast } from './utils.js';
|
|
|
18
18
|
import { resolvePreferredVendor } from './default-vendor.js';
|
|
19
19
|
import { sendFolderOp, currentFolderState } from './session-folders.js';
|
|
20
20
|
import { queueSidebarCreationEffect } from './sidebar-creation-effect.js';
|
|
21
|
+
import { isMateWorkspace } from './project-mate-navigation.js';
|
|
21
22
|
|
|
22
23
|
// Ids are unique without any module-level counter.
|
|
23
24
|
function nextId(prefix) {
|
|
@@ -66,13 +67,29 @@ export function openSessionCreate(folderId) {
|
|
|
66
67
|
}
|
|
67
68
|
var options = createOptions();
|
|
68
69
|
store.set({ sessionCreate: {
|
|
69
|
-
folderId: folderId, vendor: options.loaded ? preferredVendor(options) : "", phase: "ready", requestId: null, timer: null, error: "", notice: "",
|
|
70
|
+
mode: "provider", folderId: folderId, vendor: options.loaded ? preferredVendor(options) : "", phase: "ready", requestId: null, timer: null, error: "", notice: "",
|
|
70
71
|
defaultRequestId: null, defaultSaving: false, defaultVendor: null, focusKey: "vendor", wantFocus: false, scroll: true, restoreFocusKey: null,
|
|
71
72
|
openedAt: Date.now(),
|
|
72
73
|
} });
|
|
73
74
|
requestOptions();
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
export function beginSessionCreate(folderId) {
|
|
78
|
+
if (!isMateWorkspace(store.snap())) {
|
|
79
|
+
openSessionCreate(folderId);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
var current = createState();
|
|
83
|
+
if (current && current.phase === "pending") return false;
|
|
84
|
+
if (!current || current.mode !== "mate-direct" || current.folderId !== folderId) {
|
|
85
|
+
store.set({ sessionCreate: {
|
|
86
|
+
mode: "mate-direct", folderId: folderId, phase: "ready", requestId: null, timer: null,
|
|
87
|
+
error: "", notice: "", focusKey: "retry", wantFocus: false, scroll: true, openedAt: Date.now(),
|
|
88
|
+
} });
|
|
89
|
+
}
|
|
90
|
+
return submitMateSessionCreate();
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
export function closeSessionCreate() {
|
|
77
94
|
var current = createState();
|
|
78
95
|
if (!current || current.phase === "pending") return;
|
|
@@ -178,10 +195,35 @@ export function submitSessionCreate(startNewSession) {
|
|
|
178
195
|
return true;
|
|
179
196
|
}
|
|
180
197
|
|
|
198
|
+
export function submitMateSessionCreate() {
|
|
199
|
+
var form = createState();
|
|
200
|
+
if (!form || form.mode !== "mate-direct" || form.phase === "pending" || form.phase === "uncertain") return false;
|
|
201
|
+
var requestId = nextId("scm-");
|
|
202
|
+
var timer = setTimeout(function () { onPendingTimeout(requestId); }, PENDING_TIMEOUT_MS);
|
|
203
|
+
patch({ phase: "pending", requestId: requestId, error: "", notice: "", timer: timer, wantFocus: false });
|
|
204
|
+
if (!send({
|
|
205
|
+
type: "new_session",
|
|
206
|
+
mateDefaults: true,
|
|
207
|
+
forceNew: true,
|
|
208
|
+
requestId: requestId,
|
|
209
|
+
folderSlug: store.get('currentSlug'),
|
|
210
|
+
folderId: form.folderId === "unfiled" ? null : form.folderId,
|
|
211
|
+
})) {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
patch({ phase: "ready", requestId: null, timer: null, error: "Not connected. Reconnect and try again.", focusKey: "retry", wantFocus: true });
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
181
219
|
function onPendingTimeout(requestId) {
|
|
182
220
|
var form = createState();
|
|
183
221
|
if (!form || form.requestId !== requestId || form.phase !== "pending") return;
|
|
184
222
|
forgetLock(requestId);
|
|
223
|
+
if (form.mode === "mate-direct") {
|
|
224
|
+
patch({ phase: "uncertain", timer: null, error: "No response yet. The session may already have been created; check the list before trying again." });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
185
227
|
patch({ phase: "ready", requestId: null, timer: null, error: "No response yet. The session may already have been created; check the list first." });
|
|
186
228
|
}
|
|
187
229
|
|
|
@@ -200,7 +242,7 @@ export function handleNewSessionResult(msg) {
|
|
|
200
242
|
clearTimeout(form.timer);
|
|
201
243
|
if (!msg.ok) {
|
|
202
244
|
restoreLock(msg.requestId);
|
|
203
|
-
patch({ phase: "ready", requestId: null, timer: null, error: msg.error || "The session could not be created.", focusKey: "create", wantFocus: true });
|
|
245
|
+
patch({ phase: "ready", requestId: null, timer: null, error: msg.error || "The session could not be created.", focusKey: form.mode === "mate-direct" ? "retry" : "create", wantFocus: true });
|
|
204
246
|
return true;
|
|
205
247
|
}
|
|
206
248
|
forgetLock(msg.requestId);
|
|
@@ -300,7 +342,8 @@ function onDisconnect(form) {
|
|
|
300
342
|
if (!form) return;
|
|
301
343
|
if (form.timer) clearTimeout(form.timer);
|
|
302
344
|
if (form.phase === "pending") {
|
|
303
|
-
patch({ phase: "
|
|
345
|
+
if (form.mode === "mate-direct") patch({ phase: "uncertain", timer: null, error: "Connection lost. The session may already have been created; check the list before trying again." });
|
|
346
|
+
else patch({ phase: "ready", requestId: null, timer: null, error: "Connection lost. The session may already have been created; check the list first." });
|
|
304
347
|
}
|
|
305
348
|
if (form.defaultSaving) patch({ defaultSaving: false, defaultRequestId: null, defaultVendor: null, error: "Connection lost before the project default was confirmed." });
|
|
306
349
|
}
|
|
@@ -319,7 +362,7 @@ store.subscribe(function (state, previous) {
|
|
|
319
362
|
var form = state.sessionCreate;
|
|
320
363
|
if (previous.connected && !state.connected) onDisconnect(form);
|
|
321
364
|
// The providers are requested again after a reconnect.
|
|
322
|
-
if (!previous.connected && state.connected && form) requestOptions();
|
|
365
|
+
if (!previous.connected && state.connected && form && form.mode !== "mate-direct") requestOptions();
|
|
323
366
|
// Folder removed while its row was open: say so instead of letting a later
|
|
324
367
|
// create fail, and never silently file the session elsewhere.
|
|
325
368
|
if (form && state.sessionFolders !== previous.sessionFolders && form.folderId !== "unfiled") {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Visibility policy for the ordinary desktop and mobile session lists.
|
|
2
|
+
// Debate sessions remain in the session cache for the Debate workbench and
|
|
3
|
+
// exact-session navigation; only their ordinary-list presentation is removed.
|
|
4
|
+
|
|
5
|
+
export function isDebateOwnedSession(session) {
|
|
6
|
+
var phase = session && session.homeDebatePhase;
|
|
7
|
+
return !!(session && (
|
|
8
|
+
session.homeDebatePlanning === true
|
|
9
|
+
|| phase === "planning"
|
|
10
|
+
|| phase === "live"
|
|
11
|
+
|| phase === "ended"
|
|
12
|
+
|| phase === "interrupted"
|
|
13
|
+
|| (session.loop && session.loop.source === "debate")
|
|
14
|
+
));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ordinarySessionListSessions(sessions) {
|
|
18
|
+
var source = Array.isArray(sessions) ? sessions : [];
|
|
19
|
+
var hiddenIds = new Set();
|
|
20
|
+
for (var i = 0; i < source.length; i++) {
|
|
21
|
+
if (isDebateOwnedSession(source[i])) hiddenIds.add(source[i].id);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// A debate Driver owns its Worker rows even if an older Worker payload did
|
|
25
|
+
// not copy the debate marker. Follow parent links so those children cannot
|
|
26
|
+
// leak into the ordinary list as orphan Workers.
|
|
27
|
+
var changed = true;
|
|
28
|
+
while (changed) {
|
|
29
|
+
changed = false;
|
|
30
|
+
for (var j = 0; j < source.length; j++) {
|
|
31
|
+
var session = source[j];
|
|
32
|
+
if (hiddenIds.has(session.id) || !hiddenIds.has(session.parentSessionId)) continue;
|
|
33
|
+
hiddenIds.add(session.id);
|
|
34
|
+
changed = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return source.filter(function (session) {
|
|
39
|
+
return !hiddenIds.has(session.id);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -38,7 +38,10 @@ export function renderSessionPresence(row, sessionId) {
|
|
|
38
38
|
container.appendChild(more);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
var anchor = row.querySelector(".session-
|
|
41
|
+
var anchor = row.querySelector(".session-item-age, .mobile-worker-generation, .mobile-session-unread, .session-row-actions, .mobile-session-star");
|
|
42
|
+
// Desktop age/actions now live inside one trailing slot. insertBefore only
|
|
43
|
+
// accepts a direct child, so promote a nested match to its row-level owner.
|
|
44
|
+
while (anchor && anchor.parentNode !== row) anchor = anchor.parentNode;
|
|
42
45
|
row.insertBefore(container, anchor || null);
|
|
43
46
|
return container;
|
|
44
47
|
}
|