clay-server 5.2.0-beta.8 → 5.2.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/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/pane-pending-interactions.js +20 -0
- package/lib/pane-session-activation.js +114 -0
- package/lib/pane-session-transport.js +26 -0
- package/lib/project-session-notes.js +33 -8
- package/lib/project-sessions.js +78 -29
- package/lib/project-start-protocol.js +29 -0
- package/lib/project-vendor-login.js +2 -0
- package/lib/project.js +16 -4
- package/lib/public/app.js +41 -18
- package/lib/public/css/contextual-clay.css +2 -2
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/input.css +6 -0
- package/lib/public/css/mate-loading-scene.css +21 -0
- package/lib/public/css/overlays.css +64 -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/settings-shell.css +103 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/sticky-notes.css +37 -11
- package/lib/public/css/title-bar.css +83 -0
- package/lib/public/index.html +50 -63
- 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-connection.js +3 -0
- package/lib/public/modules/app-home-hub.js +2 -1
- package/lib/public/modules/app-message-router.js +13 -0
- package/lib/public/modules/app-messages.js +35 -7
- package/lib/public/modules/app-misc.js +3 -1
- package/lib/public/modules/app-projects.js +6 -407
- package/lib/public/modules/app-rate-limit.js +12 -1
- package/lib/public/modules/clay-owner-navigation.js +0 -3
- package/lib/public/modules/clay-studio-render.js +3 -1
- package/lib/public/modules/clay-tool-presentation.js +1 -0
- package/lib/public/modules/command-palette.js +52 -14
- package/lib/public/modules/contextual-clay-workspace.js +14 -2
- package/lib/public/modules/contextual-clay.js +19 -4
- package/lib/public/modules/contextual-mate-context.js +6 -4
- package/lib/public/modules/elicitation-form.js +5 -0
- package/lib/public/modules/github-star.js +66 -0
- package/lib/public/modules/history-replay-batch.js +8 -0
- package/lib/public/modules/home-surface-boot.js +0 -1
- package/lib/public/modules/input.js +55 -11
- package/lib/public/modules/mate-loading-scene.js +17 -0
- package/lib/public/modules/message-delivery.js +10 -2
- package/lib/public/modules/model-picker.js +2 -0
- package/lib/public/modules/pane-activation.js +214 -0
- package/lib/public/modules/pane-appearance-bridge.js +59 -0
- package/lib/public/modules/pane-binding.js +82 -0
- package/lib/public/modules/pane-bridge.js +5 -2
- package/lib/public/modules/pane-browser-bridge.js +5 -3
- package/lib/public/modules/pane-file-bridge.js +4 -0
- package/lib/public/modules/pane-issue-bridge.js +4 -1
- 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/rewind.js +5 -0
- package/lib/public/modules/server-settings.js +36 -91
- package/lib/public/modules/session-actions.js +5 -2
- package/lib/public/modules/session-context-menu.js +5 -0
- package/lib/public/modules/session-rich-drafts.js +21 -0
- package/lib/public/modules/session-switch-defer.js +67 -0
- 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/split-group-restore.js +68 -0
- package/lib/public/modules/split-pair-ui.js +1 -0
- package/lib/public/modules/split-pane-reconciler.js +15 -0
- package/lib/public/modules/split-pane-renderer.js +87 -6
- package/lib/public/modules/split-session-boundary.js +1 -1
- package/lib/public/modules/split-view.js +51 -52
- package/lib/public/modules/sticky-notes-checklist.js +51 -6
- package/lib/public/modules/stt-controller.js +4 -1
- package/lib/public/modules/stt-recording.js +1 -1
- package/lib/public/modules/theme.js +31 -3
- package/lib/public/modules/tools.js +18 -2
- 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 +10 -7
- 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 +27 -10
- 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 +5 -3
- package/lib/public/modules/server-settings-navigation.js +0 -39
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { escapeHtml } from './utils.js';
|
|
2
1
|
import { iconHtml } from './icons.js';
|
|
3
2
|
|
|
4
3
|
export function ordinaryProjects(projects) {
|
|
@@ -7,55 +6,44 @@ export function ordinaryProjects(projects) {
|
|
|
7
6
|
});
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
// Zero ordinary projects (once the list is known): the welcome renders inside the normal app shell.
|
|
10
|
+
export function isShellWelcome(state) {
|
|
11
|
+
return !!state.projectListLoaded && ordinaryProjects(state.projectsHubList).length === 0;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
return '<button type="button" class="projects-
|
|
16
|
-
'<span class="projects-action-icon">' + iconHtml(icon) + '</span><span><strong>' + title + '</strong><span>' + text + '</span></span>' + iconHtml('arrow-up-right') + '</button>';
|
|
14
|
+
function button(action, label, extra, disabled) {
|
|
15
|
+
return '<button type="button" class="projects-button ' + (extra || '') + '" data-projects-action="' + action + '"' + (disabled ? ' disabled' : '') + '>' + label + '</button>';
|
|
17
16
|
}
|
|
18
17
|
|
|
18
|
+
// Mate creation starts with the built-in Clay Mate, so the CTA waits until it is in the loaded Mate list.
|
|
19
|
+
export function clayMateReady(state) {
|
|
20
|
+
var mates = Array.isArray(state.cachedMatesList) ? state.cachedMatesList : [];
|
|
21
|
+
for (var i = 0; i < mates.length; i++) if (mates[i] && mates[i].builtinKey === 'clay') return true;
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function welcomeCard(label, title, text, action, artwork) {
|
|
26
|
+
return '<div class="projects-intro-card"><div class="projects-intro-art"><img src="/welcome-art/' + artwork + '.png" width="1536" height="1024" alt="" decoding="async"></div>' +
|
|
27
|
+
'<span class="projects-intro-label">' + label + '</span><h2>' + title + '</h2><p>' + text + '</p><div class="projects-intro-action">' + action + '</div></div>';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The standalone project list no longer exists: the hub renders only the loading state
|
|
31
|
+
// and the zero-project welcome. Once ordinary projects are known the app opens a workspace.
|
|
19
32
|
export function projectsHubContent(state) {
|
|
20
33
|
var projects = ordinaryProjects(state.projectsHubList);
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
if (!state.projectListLoaded || projects.length) {
|
|
35
|
+
return '<main class="projects-main" aria-labelledby="projects-title"><div class="projects-loading" role="status"><h1 id="projects-title" tabindex="-1">Your workspace</h1><p>Loading your workspace…</p></div></main>';
|
|
36
|
+
}
|
|
23
37
|
var allowed = state.projectsAccessLoaded && (!state.permissions || state.permissions.createProject === true);
|
|
24
38
|
var available = allowed && state.connected && !state.pendingHomeProjectSlug;
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'
|
|
32
|
-
|
|
33
|
-
'
|
|
34
|
-
|
|
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>' +
|
|
36
|
-
'<p class="projects-footnote">You can revisit this guide from Projects at any time.</p></section></main>';
|
|
37
|
-
}
|
|
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>' +
|
|
39
|
-
'<p class="projects-lead">' + (projects.length ? 'Pick up where you left off, or begin something new.' : 'Choose where your work will live. You can add more projects later.') + '</p></div>' + button('guide', 'Getting started', 'projects-button-quiet') + '</div>';
|
|
40
|
-
if (state.pendingHomeProjectSlug) html += '<p class="projects-status" role="status">Opening your project…</p>';
|
|
41
|
-
if (projects.length) {
|
|
42
|
-
html += '<div class="projects-list" aria-label="Your projects">';
|
|
43
|
-
projects.forEach(function (project) {
|
|
44
|
-
html += '<button type="button" class="projects-list-item" data-project-slug="' + escapeHtml(project.slug) + '"' + (!state.connected || state.pendingHomeProjectSlug ? ' disabled' : '') + '>' +
|
|
45
|
-
'<span class="projects-action-icon">' + iconHtml(project.isWorktree ? 'git-branch' : 'folder') + '</span><span><strong>' + escapeHtml(project.title || project.project || project.slug) + '</strong>' +
|
|
46
|
-
'<span>' + escapeHtml(project.branch || project.path || project.slug) + '</span></span>' + iconHtml('arrow-right') + '</button>';
|
|
47
|
-
});
|
|
48
|
-
html += '</div><h2 class="projects-section-title">Add a project</h2>';
|
|
49
|
-
}
|
|
50
|
-
if (!state.projectsAccessLoaded) html += '<p class="projects-status" role="status">Checking project access…</p>';
|
|
51
|
-
else if (!allowed) html += '<p class="projects-status">Ask an administrator to create or share a project with you.</p>';
|
|
52
|
-
else {
|
|
53
|
-
html += '<div class="projects-setup-actions">' +
|
|
54
|
-
setupAction('existing', 'folder-open', 'Open a folder', 'Use a directory on the machine running Clay.', !available) +
|
|
55
|
-
setupAction('create', 'plus', 'Create a project', 'Start in a new, empty project directory.', !available) +
|
|
56
|
-
setupAction('clone', 'git-branch', 'Clone a repository', 'Bring an existing Git repository into Clay.', !available) + '</div>';
|
|
57
|
-
if (!projects.length) html += '<p class="projects-next-step"><span>UP NEXT</span> Choose your AI in the workspace, then tell your Driver what you want to build or fix.</p>';
|
|
58
|
-
}
|
|
59
|
-
if (!state.connected) html += '<p class="projects-status" role="status">Reconnecting… Project actions will be available when the connection returns.</p>';
|
|
60
|
-
return html + '</main><footer class="projects-footer">Your projects, conversations, and tools. One workspace.</footer>';
|
|
39
|
+
var mateReady = !!state.connected && clayMateReady(state);
|
|
40
|
+
return '<main class="projects-main" aria-labelledby="projects-title"><section class="projects-welcome"><div class="projects-eyebrow">WELCOME TO</div>' +
|
|
41
|
+
'<h1 id="projects-title" tabindex="-1"><span class="projects-wordmark" role="img" aria-label="Clay Studio"></span></h1>' +
|
|
42
|
+
'<p class="projects-lead">Where the minds work together</p>' +
|
|
43
|
+
'<div class="projects-intro-grid">' +
|
|
44
|
+
welcomeCard('01 / PROJECTS', 'Work together on a project.', 'Bring a codebase or a folder of files, and work through it together with Clay Studio.',
|
|
45
|
+
button('create-project', 'Start a project ' + iconHtml('arrow-right'), 'projects-button-primary', !available), 'project-clay') +
|
|
46
|
+
welcomeCard('02 / MATES', 'Create your own specialist.', 'Create a Mate with the expertise you need, and work together on your ideas and tasks.',
|
|
47
|
+
button('mate', 'Create a Mate ' + iconHtml('arrow-right'), 'projects-button-secondary', !mateReady), 'mate-clay') +
|
|
48
|
+
'</div></section></main>';
|
|
61
49
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { switchProject } from './app-projects.js';
|
|
4
|
+
import { openAddProjectModal } from './project-start-dialog.js';
|
|
5
|
+
import { startClayMateCreation } from './project-mate-creation-workspace.js';
|
|
6
|
+
import { updateHomeSurfacePreference } from './home-surface.js';
|
|
7
|
+
import { projectsHubContent, isShellWelcome, clayMateReady, ordinaryProjects } from './projects-hub-view.js';
|
|
7
8
|
import { refreshIcons } from './icons.js';
|
|
8
9
|
|
|
9
10
|
function root() { return document.getElementById('projects-hub'); }
|
|
@@ -11,17 +12,18 @@ function root() { return document.getElementById('projects-hub'); }
|
|
|
11
12
|
function render() {
|
|
12
13
|
var element = root();
|
|
13
14
|
if (!element || !store.get('projectsHubVisible')) return;
|
|
15
|
+
document.body.classList.toggle('projects-shell', isShellWelcome(store.snap()));
|
|
14
16
|
var html = projectsHubContent(store.snap());
|
|
15
17
|
if (store.get('projectsHubMarkup') === html) return;
|
|
16
18
|
store.set({ projectsHubMarkup: html });
|
|
17
19
|
var active = element.contains(document.activeElement) ? document.activeElement : null;
|
|
18
|
-
var action = active && (active.dataset.projectsAction
|
|
20
|
+
var action = active && (active.dataset.projectsAction);
|
|
19
21
|
element.innerHTML = html;
|
|
20
22
|
refreshIcons();
|
|
21
23
|
if (action) {
|
|
22
24
|
var buttons = element.querySelectorAll('button, a');
|
|
23
25
|
for (var i = 0; i < buttons.length; i++) {
|
|
24
|
-
if (
|
|
26
|
+
if (buttons[i].dataset.projectsAction === action && !buttons[i].disabled) {
|
|
25
27
|
buttons[i].focus({ preventScroll: true });
|
|
26
28
|
break;
|
|
27
29
|
}
|
|
@@ -35,23 +37,39 @@ function focusHeading() {
|
|
|
35
37
|
if (title) title.focus({ preventScroll: true });
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// The authorised project to open when no standalone list exists: the server-saved
|
|
41
|
+
// last project, then the current one, then the first ordinary project. Only
|
|
42
|
+
// projects present in the server-provided list qualify.
|
|
43
|
+
function workspaceFallback() {
|
|
44
|
+
var known = ordinaryProjects(store.get('projectsHubList')).map(function (project) { return project.slug; });
|
|
45
|
+
var choices = [store.get('homeSurfaceProjectSlug'), store.get('currentSlug'), known[0]];
|
|
46
|
+
for (var i = 0; i < choices.length; i++) if (choices[i] && known.indexOf(choices[i]) !== -1) return choices[i];
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Shows the loading or zero-project welcome; with projects present it opens a workspace instead.
|
|
38
51
|
export function showProjectsHub(fromHistory) {
|
|
52
|
+
if (store.get('projectListLoaded') && ordinaryProjects(store.get('projectsHubList')).length) {
|
|
53
|
+
var slug = workspaceFallback();
|
|
54
|
+
if (slug) {
|
|
55
|
+
hideProjectsHub();
|
|
56
|
+
if (fromHistory) history.replaceState(null, '', '/p/' + slug + '/');
|
|
57
|
+
switchProject(slug);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
39
61
|
store.set({ projectsHubVisible: true, pendingHomeProjectSlug: null });
|
|
40
62
|
document.body.classList.add('projects-active');
|
|
41
63
|
root().classList.remove('hidden');
|
|
42
|
-
|
|
43
|
-
if (!fromHistory && location.pathname !== '/projects') {
|
|
44
|
-
var method = document.documentElement.classList.contains('pwa-standalone') ? 'replaceState' : 'pushState';
|
|
45
|
-
history[method](null, '', '/projects');
|
|
46
|
-
}
|
|
64
|
+
if (location.pathname === '/projects' || location.pathname === '/projects/') history.replaceState(null, '', '/');
|
|
47
65
|
render();
|
|
48
66
|
focusHeading();
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
export function hideProjectsHub() {
|
|
52
70
|
if (!store.get('projectsHubVisible')) return;
|
|
53
|
-
store.set({ projectsHubVisible: false
|
|
54
|
-
document.body.classList.remove('projects-active');
|
|
71
|
+
store.set({ projectsHubVisible: false });
|
|
72
|
+
document.body.classList.remove('projects-active', 'projects-shell');
|
|
55
73
|
if (root()) root().classList.add('hidden');
|
|
56
74
|
}
|
|
57
75
|
|
|
@@ -61,25 +79,17 @@ export function completeProjectsOnboarding() {
|
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
function handleClick(event) {
|
|
64
|
-
var button = event.target.closest('[data-projects-action]
|
|
82
|
+
var button = event.target.closest('[data-projects-action]');
|
|
65
83
|
if (!button || button.disabled) return;
|
|
66
84
|
event.preventDefault();
|
|
67
85
|
var action = button.dataset.projectsAction;
|
|
68
|
-
if (action === 'settings') { openUserSettings(); return; }
|
|
69
|
-
if (action === 'guide') { store.set({ projectsShowGuide: true }); render(); focusHeading(); return; }
|
|
70
|
-
if (action === 'start') {
|
|
71
|
-
store.set({ projectsShowGuide: false });
|
|
72
|
-
updateHomeSurfacePreference({ onboardingStep: 'project' });
|
|
73
|
-
render(); focusHeading();
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
86
|
var ws = getWs();
|
|
77
87
|
if (!ws || ws.readyState !== 1) return;
|
|
78
|
-
if (
|
|
79
|
-
if (
|
|
88
|
+
if (action === 'mate') { if (clayMateReady(store.snap())) startClayMateCreation(); return; }
|
|
89
|
+
if (action === 'create-project') {
|
|
80
90
|
var permissions = store.get('permissions');
|
|
81
|
-
if (!store.get('projectsAccessLoaded') || (permissions && permissions.createProject !== true)) return;
|
|
82
|
-
openAddProjectModal(
|
|
91
|
+
if (!store.get('projectsAccessLoaded') || store.get('pendingHomeProjectSlug') || (permissions && permissions.createProject !== true)) return;
|
|
92
|
+
openAddProjectModal();
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
|
|
@@ -88,14 +98,15 @@ export function initProjectsHub() {
|
|
|
88
98
|
store.subscribe(function (state, previous) {
|
|
89
99
|
if (state.projectsHubRestoreRequested && !previous.projectsHubRestoreRequested) {
|
|
90
100
|
showProjectsHub(true);
|
|
91
|
-
if (location.pathname === '/') history.replaceState(null, '', '/projects');
|
|
92
101
|
store.set({ projectsHubRestoreRequested: false });
|
|
93
102
|
return;
|
|
94
103
|
}
|
|
95
104
|
if (!state.projectsHubVisible) return;
|
|
105
|
+
// A project already being opened keeps its own activation; otherwise known projects replace the hub.
|
|
106
|
+
if (state.projectListLoaded && !state.pendingHomeProjectSlug && ordinaryProjects(state.projectsHubList).length) { showProjectsHub(true); return; }
|
|
96
107
|
if (state.projectsHubList !== previous.projectsHubList || state.projectListLoaded !== previous.projectListLoaded ||
|
|
97
108
|
state.homeSurfaceLoaded !== previous.homeSurfaceLoaded || state.onboardingStep !== previous.onboardingStep ||
|
|
98
109
|
state.projectsAccessLoaded !== previous.projectsAccessLoaded || state.permissions !== previous.permissions ||
|
|
99
|
-
state.connected !== previous.connected || state.pendingHomeProjectSlug !== previous.pendingHomeProjectSlug) render();
|
|
110
|
+
state.connected !== previous.connected || state.cachedMatesList !== previous.cachedMatesList || state.pendingHomeProjectSlug !== previous.pendingHomeProjectSlug) render();
|
|
100
111
|
});
|
|
101
112
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { captureSessionAction, sessionActionCurrent } from './pane-binding.js';
|
|
3
5
|
|
|
4
6
|
var ctx;
|
|
5
7
|
var rewindMode = false;
|
|
@@ -81,6 +83,7 @@ function initiateRewind(uuid) {
|
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
85
|
pendingRewindUuid = uuid;
|
|
86
|
+
store.set({ rewindAction: captureSessionAction() });
|
|
84
87
|
rewindPreviewInFlight = true;
|
|
85
88
|
if (ctx.ws && ctx.connected) {
|
|
86
89
|
ctx.ws.send(JSON.stringify({ type: "rewind_preview", uuid: uuid }));
|
|
@@ -120,6 +123,7 @@ function updateSummaryForMode() {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
export function showRewindModal(data) {
|
|
126
|
+
if (!sessionActionCurrent(store.get('rewindAction'))) return;
|
|
123
127
|
rewindPreviewInFlight = false;
|
|
124
128
|
|
|
125
129
|
// Ignore stale preview results that don't match current pending UUID
|
|
@@ -367,6 +371,7 @@ export function initRewind(_ctx) {
|
|
|
367
371
|
});
|
|
368
372
|
|
|
369
373
|
rewindConfirmBtn.addEventListener("click", function() {
|
|
374
|
+
if (!sessionActionCurrent(store.get('rewindAction'))) { hideRewindModal(); return; }
|
|
370
375
|
if (rewindExecuting) return;
|
|
371
376
|
if (pendingRewindUuid && ctx.ws && ctx.connected) {
|
|
372
377
|
rewindExecuting = true;
|
|
@@ -1,46 +1,28 @@
|
|
|
1
1
|
import { initStorageUsage, loadStorageUsage } from './server-storage-settings.js';
|
|
2
2
|
import { initAccountAuthSettings, loadAccountAuthSettings } from './account-auth-settings.js';
|
|
3
|
-
//
|
|
3
|
+
// Server pane of the unified settings shell. Page state survives navigation and closing.
|
|
4
4
|
import { store } from './store.js';
|
|
5
5
|
import { getWs } from './ws-ref.js';
|
|
6
6
|
import { refreshIcons } from './icons.js';
|
|
7
7
|
import { closeFileViewer } from './filebrowser.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { loadAdminSection } from './admin.js';
|
|
9
|
+
import { registerSettingsScope, isSettingsShellOpen, activeSettingsScope } from './settings-shell.js';
|
|
10
10
|
import { initNetworkSettings, loadNetworkSettings } from './server-network-settings.js';
|
|
11
11
|
import { initServerControls, setServerPinAvailability } from './server-settings-controls.js';
|
|
12
12
|
import { initServerEnvironment, loadSharedEnv } from './server-settings-environment.js';
|
|
13
|
-
import {
|
|
14
|
-
import { hasSettingsDrafts, confirmSettingsAction, trapSettingsFocus } from './server-settings-state.js';
|
|
13
|
+
import { hasSettingsDrafts } from './server-settings-state.js';
|
|
15
14
|
import { enhanceSettingsSelects } from './settings-select.js';
|
|
16
15
|
export { updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleServerSettingResult } from './server-settings-controls.js';
|
|
17
16
|
export { handleSharedEnv, handleSharedEnvSaved } from './server-settings-environment.js';
|
|
18
17
|
|
|
19
|
-
function isolateSettings(active) {
|
|
20
|
-
if (active) {
|
|
21
|
-
var entries = Array.from(document.body.children).filter(function (el) { return el !== root() && el.id !== 'confirm-modal' && el.tagName !== 'SCRIPT' && el.tagName !== 'STYLE'; }).map(function (el) { var entry = { el: el, inert: el.inert, hidden: el.getAttribute('aria-hidden') }; el.inert = true; el.setAttribute('aria-hidden', 'true'); return entry; });
|
|
22
|
-
store.set({ serverSettingsInert: entries });
|
|
23
|
-
} else {
|
|
24
|
-
(store.get('serverSettingsInert') || []).forEach(function (entry) { entry.el.inert = entry.inert; if (entry.hidden === null) entry.el.removeAttribute('aria-hidden'); else entry.el.setAttribute('aria-hidden', entry.hidden); });
|
|
25
|
-
store.set({ serverSettingsInert: [] });
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
18
|
function root() { return document.getElementById('server-settings'); }
|
|
29
|
-
export function isSettingsOpen() { return
|
|
19
|
+
export function isSettingsOpen() { return isSettingsShellOpen() && activeSettingsScope() === 'server'; }
|
|
30
20
|
function send(type) { var ws = getWs(); if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: type })); }
|
|
31
21
|
|
|
32
|
-
function selectPage(id
|
|
22
|
+
function selectPage(id) {
|
|
33
23
|
if (!store.get('serverSettingsAuthorized')) return;
|
|
34
|
-
if (id === 'admin-invites') id = 'admin-users';
|
|
35
|
-
var nav = document.getElementById('settings-page-nav');
|
|
36
|
-
if (!nav.querySelector('[data-section="' + id + '"]')) id = 'overview';
|
|
37
24
|
store.set({ serverSettingsPage: id });
|
|
38
25
|
root().querySelectorAll('.server-settings-section').forEach(function (page) { page.classList.toggle('active', page.dataset.section === id); });
|
|
39
|
-
nav.querySelectorAll('button').forEach(function (button) {
|
|
40
|
-
button.classList.toggle('active', button.dataset.section === id);
|
|
41
|
-
if (button.dataset.section === id) button.setAttribute('aria-current', 'page'); else button.removeAttribute('aria-current');
|
|
42
|
-
});
|
|
43
|
-
document.getElementById('settings-nav-dropdown').value = id;
|
|
44
26
|
if (id === 'network') loadNetworkSettings();
|
|
45
27
|
if (id === 'security') loadAccountAuthSettings();
|
|
46
28
|
if (id === 'storage') loadStorageUsage();
|
|
@@ -49,85 +31,48 @@ function selectPage(id, focus) {
|
|
|
49
31
|
var body = document.getElementById(id + '-body');
|
|
50
32
|
if (id !== 'admin-smtp' || !(store.get('serverSettingsDrafts') || {}).smtp) loadAdminSection(id, body);
|
|
51
33
|
}
|
|
52
|
-
|
|
53
|
-
var heading = root().querySelector('.server-settings-section.active h2');
|
|
54
|
-
heading.tabIndex = -1; heading.focus();
|
|
55
|
-
}
|
|
34
|
+
enhanceSettingsSelects(root()); refreshIcons(root());
|
|
56
35
|
}
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
|
|
37
|
+
// The shell has already verified administrator (or solo-owner) access.
|
|
38
|
+
function enterServerSettings(ctx) {
|
|
60
39
|
closeFileViewer();
|
|
61
|
-
isolateSettings(true);
|
|
62
|
-
store.set({ serverSettingsReturnFocus: document.activeElement });
|
|
63
|
-
root().classList.remove('hidden');
|
|
64
|
-
document.getElementById('server-settings-btn').classList.add('active');
|
|
65
|
-
document.getElementById('server-settings-close').focus();
|
|
66
40
|
var status = document.getElementById('settings-access-status');
|
|
67
|
-
status.textContent = '
|
|
68
|
-
store.set({ serverSettingsAuthorized:
|
|
69
|
-
|
|
70
|
-
if (serverPinSection) serverPinSection.hidden = true;
|
|
41
|
+
status.textContent = '';
|
|
42
|
+
store.set({ serverSettingsAuthorized: true });
|
|
43
|
+
setServerPinAvailability(!ctx.access.multiUser);
|
|
71
44
|
root().querySelectorAll('.server-settings-section').forEach(function (page) { page.classList.remove('active'); });
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!isSettingsOpen()) return;
|
|
84
|
-
store.set({ serverSettingsAuthorized: true });
|
|
85
|
-
setServerPinAvailability(!data.multiUser);
|
|
86
|
-
status.textContent = '';
|
|
87
|
-
renderSettingsNavigation(!!data.multiUser, selectPage);
|
|
88
|
-
selectPage(store.get('serverSettingsPage') || 'overview', false);
|
|
89
|
-
if (!hasSettingsDrafts()) send('get_daemon_config');
|
|
90
|
-
setTimeout(function () {
|
|
91
|
-
if (isSettingsOpen() && !store.get('serverSettingsConfigLoaded')) status.textContent = 'Server configuration has not loaded. Reconnect, then reopen Settings to retry.';
|
|
92
|
-
}, 12000);
|
|
93
|
-
send('process_stats');
|
|
94
|
-
clearInterval(store.get('serverSettingsStatsTimer'));
|
|
95
|
-
store.set({ serverSettingsStatsTimer: setInterval(function () { send('process_stats'); }, 5000) });
|
|
96
|
-
document.getElementById('settings-project-slug').textContent = store.get('currentSlug') || '(none)';
|
|
97
|
-
document.getElementById('settings-ws-path').textContent = store.get('wsPath') || '/ws';
|
|
98
|
-
var pill = document.getElementById('skip-perms-pill');
|
|
99
|
-
document.getElementById('settings-skip-perms').textContent = pill && !pill.classList.contains('hidden') ? 'Skip' : 'Permission checks';
|
|
100
|
-
enhanceSettingsSelects(root()); refreshIcons(root());
|
|
101
|
-
} catch (error) { status.textContent = error.message; }
|
|
45
|
+
if (!hasSettingsDrafts()) send('get_daemon_config');
|
|
46
|
+
setTimeout(function () {
|
|
47
|
+
if (isSettingsOpen() && !store.get('serverSettingsConfigLoaded')) status.textContent = 'Server configuration has not loaded. Reconnect, then reopen Settings to retry.';
|
|
48
|
+
}, 12000);
|
|
49
|
+
send('process_stats');
|
|
50
|
+
clearInterval(store.get('serverSettingsStatsTimer'));
|
|
51
|
+
store.set({ serverSettingsStatsTimer: setInterval(function () { send('process_stats'); }, 5000) });
|
|
52
|
+
document.getElementById('settings-project-slug').textContent = store.get('currentSlug') || '(none)';
|
|
53
|
+
document.getElementById('settings-ws-path').textContent = store.get('wsPath') || '/ws';
|
|
54
|
+
var pill = document.getElementById('skip-perms-pill');
|
|
55
|
+
document.getElementById('settings-skip-perms').textContent = pill && !pill.classList.contains('hidden') ? 'Skip' : 'Permission checks';
|
|
102
56
|
}
|
|
103
57
|
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
root().classList.add('hidden');
|
|
108
|
-
document.getElementById('server-settings-btn').classList.remove('active');
|
|
109
|
-
clearInterval(store.get('serverSettingsStatsTimer'));
|
|
110
|
-
var previous = store.get('serverSettingsReturnFocus');
|
|
111
|
-
if (previous && previous.isConnected) previous.focus();
|
|
112
|
-
}
|
|
113
|
-
if (hasSettingsDrafts()) confirmSettingsAction('You have unsaved changes. Closing keeps these drafts in this tab, but reloading the page will lose them.', 'Close with drafts', close);
|
|
114
|
-
else close();
|
|
58
|
+
function leaveServerSettings() {
|
|
59
|
+
clearInterval(store.get('serverSettingsStatsTimer'));
|
|
60
|
+
store.set({ serverSettingsAuthorized: false });
|
|
115
61
|
}
|
|
116
62
|
|
|
117
63
|
export function initServerSettings() {
|
|
118
64
|
if (!root()) return;
|
|
119
65
|
initNetworkSettings(); initServerControls(); initServerEnvironment(); initStorageUsage(); initAccountAuthSettings();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
trapSettingsFocus(event, root());
|
|
66
|
+
registerSettingsScope('server', {
|
|
67
|
+
pane: root(),
|
|
68
|
+
enter: enterServerSettings,
|
|
69
|
+
activate: selectPage,
|
|
70
|
+
leave: leaveServerSettings,
|
|
71
|
+
leavePrompt: function (kind) {
|
|
72
|
+
if (!hasSettingsDrafts() || kind === 'page' || kind === 'project') return null;
|
|
73
|
+
if (kind === 'close') return { message: 'You have unsaved changes. Closing keeps these drafts in this tab, but reloading the page will lose them.', action: 'Close with drafts' };
|
|
74
|
+
return { message: 'Your server settings drafts will remain in this tab while you visit another settings area.', action: 'Leave Server settings' };
|
|
75
|
+
}
|
|
131
76
|
});
|
|
132
77
|
new MutationObserver(function () { enhanceSettingsSelects(root()); }).observe(root(), { childList: true, subtree: true });
|
|
133
78
|
}
|
|
@@ -4,6 +4,7 @@ import { addSystemMessage, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.
|
|
|
4
4
|
import { openPairDialog } from './split-pair-ui.js';
|
|
5
5
|
import { buildAgentVendorSelect, fillAgentModels, buildAgentEffortSelect, fillAgentEffort } from './agent-config-selects.js';
|
|
6
6
|
import { showToast } from './utils.js';
|
|
7
|
+
import { sessionCallback, captureSessionAction, sessionActionCurrent } from './pane-binding.js';
|
|
7
8
|
|
|
8
9
|
var addWorkerButton = null;
|
|
9
10
|
var handoffButton = null;
|
|
@@ -33,6 +34,7 @@ function handoffField(labelText, control) {
|
|
|
33
34
|
function requestHandoffDialog() {
|
|
34
35
|
var ws = getWs();
|
|
35
36
|
if (!ws || ws.readyState !== 1) return;
|
|
37
|
+
store.set({ handoffAction: captureSessionAction() });
|
|
36
38
|
ws.send(JSON.stringify({ type: "handoff_session_options" }));
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -47,6 +49,7 @@ function openWorkerDialog() {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function showHandoffDialog(options) {
|
|
52
|
+
if (!sessionActionCurrent(store.get('handoffAction'))) return;
|
|
50
53
|
closeHandoffDialog();
|
|
51
54
|
var installed = options.installedVendors || [];
|
|
52
55
|
var vendorInfo = store.get('vendorInfo') || {};
|
|
@@ -147,7 +150,7 @@ function showHandoffDialog(options) {
|
|
|
147
150
|
if (event.key === "Escape") closeHandoffDialog();
|
|
148
151
|
};
|
|
149
152
|
document.addEventListener("keydown", handoffEscapeHandler);
|
|
150
|
-
submit.addEventListener("click", function () {
|
|
153
|
+
submit.addEventListener("click", sessionCallback(function () {
|
|
151
154
|
var ws = getWs();
|
|
152
155
|
if (!ws || ws.readyState !== 1) return;
|
|
153
156
|
ws.send(JSON.stringify({
|
|
@@ -158,7 +161,7 @@ function showHandoffDialog(options) {
|
|
|
158
161
|
}));
|
|
159
162
|
submit.disabled = true;
|
|
160
163
|
submit.textContent = "Continuing…";
|
|
161
|
-
});
|
|
164
|
+
}));
|
|
162
165
|
vendorSelect.focus();
|
|
163
166
|
}
|
|
164
167
|
|
|
@@ -109,6 +109,8 @@ function currentRenameCanSend(state) {
|
|
|
109
109
|
String(store.get('activeSessionId')) === String(state.sessionId) :
|
|
110
110
|
state && state.row && String(state.row.dataset.sessionId) === String(state.sessionId);
|
|
111
111
|
return !!(state && state.row && state.row.isConnected && sameSession &&
|
|
112
|
+
(state.source !== 'header' || state.origin === store.get('sessionOriginId')) &&
|
|
113
|
+
state.epoch === (store.get('sessionActionEpoch') || 0) && !store.get('panePendingBinding') && !store.get('deferredSessionSwitch') &&
|
|
112
114
|
store.get('connected') && state.ws === ws && ws && ws.readyState === 1 &&
|
|
113
115
|
state.slug === store.get('currentSlug') &&
|
|
114
116
|
(!permissions || permissions.sessionRename !== false));
|
|
@@ -158,6 +160,8 @@ function beginSessionInlineRename(row, title, control, session, source, inputCla
|
|
|
158
160
|
input: input,
|
|
159
161
|
originalNodes: originalNodes,
|
|
160
162
|
sessionId: session.id,
|
|
163
|
+
origin: session.sessionOriginId,
|
|
164
|
+
epoch: store.get('sessionActionEpoch') || 0,
|
|
161
165
|
currentTitle: session.title || "New Session",
|
|
162
166
|
control: control || null,
|
|
163
167
|
controlDisplay: controlDisplay,
|
|
@@ -202,6 +206,7 @@ export function startHeaderSessionInlineRename(title, control, session) {
|
|
|
202
206
|
|
|
203
207
|
store.subscribe(function (state, previous) {
|
|
204
208
|
if (state.currentSlug !== previous.currentSlug || state.dmMode !== previous.dmMode ||
|
|
209
|
+
state.sessionActionEpoch !== previous.sessionActionEpoch || state.panePendingBinding !== previous.panePendingBinding ||
|
|
205
210
|
(previous.connected && !state.connected)) {
|
|
206
211
|
closeSessionContextMenu(false);
|
|
207
212
|
cancelSessionInlineRename(false);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { takeComposerDraft, restoreComposerDraft } from './input.js';
|
|
3
|
+
|
|
4
|
+
function key() { return [store.get('myUserId') || '', store.get('currentSlug') || '', store.get('sessionOriginId') || ''].join(':'); }
|
|
5
|
+
export function checkComposerDeparture() {
|
|
6
|
+
var draft = takeComposerDraft();
|
|
7
|
+
restoreComposerDraft(draft);
|
|
8
|
+
}
|
|
9
|
+
export function saveSessionRichDraft() {
|
|
10
|
+
if (store.get('activeProjectMateId') || !store.get('sessionOriginId') || store.get('panePendingBinding')) return;
|
|
11
|
+
var drafts = Object.assign({}, store.get('sessionRichDrafts') || {});
|
|
12
|
+
var draft = takeComposerDraft();
|
|
13
|
+
drafts[key()] = draft;
|
|
14
|
+
restoreComposerDraft(draft);
|
|
15
|
+
store.set({ sessionRichDrafts: drafts });
|
|
16
|
+
}
|
|
17
|
+
export function restoreSessionRichDraft() {
|
|
18
|
+
if (store.get('activeProjectMateId') || store.get('panePendingBinding')) return;
|
|
19
|
+
var draft = (store.get('sessionRichDrafts') || {})[key()];
|
|
20
|
+
restoreComposerDraft(draft || { text: (store.get('sessionDrafts') || {})[store.get('activeSessionId')] || '', start: 0, end: 0 });
|
|
21
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { composerDepartureReason } from './input.js';
|
|
4
|
+
import { processMessage } from './app-message-router.js';
|
|
5
|
+
|
|
6
|
+
function notice(show, message) {
|
|
7
|
+
var element = document.getElementById('session-switch-wait');
|
|
8
|
+
if (!show) { if (element) element.remove(); return; }
|
|
9
|
+
if (element) { if (message) element.textContent = message; return; }
|
|
10
|
+
var area = document.getElementById('input-area');
|
|
11
|
+
if (!area) return;
|
|
12
|
+
element = document.createElement('div'); element.id = 'session-switch-wait';
|
|
13
|
+
element.className = 'session-switch-wait'; element.setAttribute('role', 'status');
|
|
14
|
+
element.textContent = 'Session change waiting. Finish or discard voice input, or wait for attachments. Your draft stays with this conversation.';
|
|
15
|
+
area.prepend(element);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Keep the source composer and its async completions together. The destination
|
|
19
|
+
// replay waits in order; it cannot change identity midway through an upload.
|
|
20
|
+
export function deferSessionSwitch(message) {
|
|
21
|
+
var pending = store.get('deferredSessionSwitch');
|
|
22
|
+
if (pending && pending.draining) return false;
|
|
23
|
+
if (['account_auth_required', 'project_removed', 'project_access_revoked'].indexOf(message.type) >= 0) {
|
|
24
|
+
store.set({ deferredSessionSwitch: null }); notice(false); return false;
|
|
25
|
+
}
|
|
26
|
+
if (!pending && message.type === 'session_switched' && composerDepartureReason()) {
|
|
27
|
+
pending = { socket: getWs(), projectSlug: store.get('currentSlug'), ownerId: store.get('myUserId'),
|
|
28
|
+
sessionId: store.get('activeSessionId'), origin: store.get('sessionOriginId'), processing: store.get('processing'), messages: [] };
|
|
29
|
+
var speech = store.get('speechActive');
|
|
30
|
+
if (speech) speech.autoSend = false;
|
|
31
|
+
store.set({ deferredSessionSwitch: pending }); notice(true);
|
|
32
|
+
}
|
|
33
|
+
if (!pending) return false;
|
|
34
|
+
if (pending.projectSlug === store.get('currentSlug') && pending.ownerId === store.get('myUserId')) pending.socket = getWs();
|
|
35
|
+
if (store.get('connected') && pending.processing && !store.get('processing')) store.set({ processing: true });
|
|
36
|
+
pending.messages.push(message);
|
|
37
|
+
if (message.type === 'session_list' || message.type === 'session_switched') queueMicrotask(drain);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function drain() {
|
|
42
|
+
var pending = store.get('deferredSessionSwitch');
|
|
43
|
+
if (!pending || pending.draining || composerDepartureReason() || !store.get('connected')) return;
|
|
44
|
+
if (pending.socket !== getWs() || pending.projectSlug !== store.get('currentSlug') || pending.ownerId !== store.get('myUserId')) return;
|
|
45
|
+
var latestSwitch, latestList;
|
|
46
|
+
pending.messages.forEach(function (message) {
|
|
47
|
+
if (message.type === 'session_switched') latestSwitch = message;
|
|
48
|
+
if (message.type === 'session_list') latestList = message;
|
|
49
|
+
});
|
|
50
|
+
if (!latestSwitch || !latestList) return;
|
|
51
|
+
var target = (latestList.sessions || []).find(function (session) { return session.id === latestSwitch.id; });
|
|
52
|
+
if (!target || target.sessionOriginId !== latestSwitch.sessionOriginId) {
|
|
53
|
+
notice(true, 'The conversation changed while your draft was waiting. Your draft is preserved. Choose a conversation to continue.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Restoring superseded snapshots must not trigger outbound session effects
|
|
57
|
+
// before the final authoritative target is installed.
|
|
58
|
+
pending.draining = true;
|
|
59
|
+
pending.finalSwitch = latestSwitch;
|
|
60
|
+
pending.messages.forEach(processMessage);
|
|
61
|
+
store.set({ deferredSessionSwitch: null }); notice(false);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
store.subscribe(function (state, previous) {
|
|
65
|
+
if (state.deferredSessionSwitch && (state.composerUploadingCount !== previous.composerUploadingCount ||
|
|
66
|
+
state.speechActive !== previous.speechActive || state.speechRetry !== previous.speechRetry || state.connected !== previous.connected)) queueMicrotask(drain);
|
|
67
|
+
});
|