clay-server 5.2.1-beta.2 → 5.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/clay-control-presentation-binding.js +5 -3
- package/lib/clay-product-control-tools.js +3 -3
- package/lib/clay-product-control.js +4 -2
- package/lib/clay-screen-controls.js +18 -3
- package/lib/clay-split-view-awareness.js +35 -0
- package/lib/clay-split-view-controls.js +122 -0
- package/lib/clay-split-view-input.js +20 -0
- package/lib/clay-split-view-render-proof.js +22 -0
- package/lib/pane-asset-manifest.js +63 -0
- package/lib/project-connection.js +19 -10
- package/lib/project-session-actions.js +1 -0
- package/lib/project-session-delete.js +2 -2
- package/lib/project-session-folders.js +1 -0
- package/lib/project-sessions.js +6 -1
- package/lib/project-split-view-document.js +33 -0
- package/lib/project-split-view-names.js +67 -0
- package/lib/project-split-view-organization.js +23 -0
- package/lib/project-split-views.js +479 -0
- package/lib/project-worker-conversation.js +158 -0
- package/lib/project-worker-creation-proposal.js +3 -4
- package/lib/project-worker-proposal.js +1 -1
- package/lib/project-worker-removal.js +101 -0
- package/lib/project.js +28 -1
- package/lib/public/app.js +32 -13
- package/lib/public/css/base.css +9 -0
- package/lib/public/css/filebrowser.css +0 -1
- package/lib/public/css/pane-header-controls.css +100 -0
- package/lib/public/css/pane.css +24 -5
- package/lib/public/css/popup-chat.css +123 -0
- package/lib/public/css/right-workbench.css +50 -18
- package/lib/public/css/split-view-layout.css +460 -0
- package/lib/public/css/split-view-worker-activity.css +97 -0
- package/lib/public/css/sticky-notes.css +13 -0
- package/lib/public/index.html +3 -3
- package/lib/public/modules/app-connection.js +4 -1
- package/lib/public/modules/app-messages.js +25 -8
- package/lib/public/modules/chat-render-runtime.js +45 -0
- package/lib/public/modules/clay-control-copy.js +3 -0
- package/lib/public/modules/clay-navigation-render.js +1 -1
- package/lib/public/modules/clay-owner-navigation.js +15 -5
- package/lib/public/modules/clay-split-view-screen.js +74 -0
- package/lib/public/modules/clay-tool-activity.js +5 -0
- package/lib/public/modules/command-palette.js +30 -28
- package/lib/public/modules/contextual-clay-session.js +26 -2
- package/lib/public/modules/contextual-clay.js +5 -2
- package/lib/public/modules/contextual-mate-sessions.js +2 -0
- package/lib/public/modules/generation-stop.js +7 -1
- package/lib/public/modules/knowledge-workbench-entry.js +43 -0
- package/lib/public/modules/markdown.js +11 -0
- package/lib/public/modules/mate-knowledge-workbench.js +2 -7
- package/lib/public/modules/mate-sidebar.js +1 -1
- package/lib/public/modules/palette-clay-conversation.js +87 -0
- package/lib/public/modules/palette-render-helpers.js +25 -0
- package/lib/public/modules/pane-activation.js +46 -7
- package/lib/public/modules/pane-binding.js +12 -2
- package/lib/public/modules/pane-bridge.js +12 -3
- package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
- package/lib/public/modules/pane-feature-assets.js +35 -0
- package/lib/public/modules/pane-search-receipt.js +12 -0
- package/lib/public/modules/permission-control.js +6 -6
- package/lib/public/modules/popup-chat-rendering.js +244 -0
- package/lib/public/modules/project-activation.js +2 -2
- package/lib/public/modules/qrcode.js +12 -1
- package/lib/public/modules/right-workbench.js +68 -0
- package/lib/public/modules/search-hints.js +79 -0
- package/lib/public/modules/session-context-menu.js +35 -2
- package/lib/public/modules/session-folder-context.js +24 -13
- package/lib/public/modules/session-folder-delete.js +18 -1
- package/lib/public/modules/session-folder-layout.js +7 -6
- package/lib/public/modules/session-search-pane-state.js +30 -0
- package/lib/public/modules/session-search.js +53 -13
- package/lib/public/modules/sidebar-mobile.js +3 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
- package/lib/public/modules/sidebar-sessions.js +57 -24
- package/lib/public/modules/sidebar.js +20 -3
- package/lib/public/modules/split-group-helpers.js +1 -1
- package/lib/public/modules/split-group-restore.js +8 -11
- package/lib/public/modules/split-pair-ui.js +18 -0
- package/lib/public/modules/split-pane-header-state.js +45 -0
- package/lib/public/modules/split-pane-reconciler.js +23 -4
- package/lib/public/modules/split-pane-renderer.js +330 -23
- package/lib/public/modules/split-pane-retention.js +157 -0
- package/lib/public/modules/split-session-boundary.js +21 -0
- package/lib/public/modules/split-view-create-action.js +15 -0
- package/lib/public/modules/split-view-create-menu.js +83 -0
- package/lib/public/modules/split-view-create-request.js +23 -0
- package/lib/public/modules/split-view-layout-client.js +198 -0
- package/lib/public/modules/split-view-layout-icons.js +37 -0
- package/lib/public/modules/split-view-layout-scope.js +38 -0
- package/lib/public/modules/split-view-organization.js +73 -0
- package/lib/public/modules/split-view-presentation-close.js +35 -0
- package/lib/public/modules/split-view-presentation-drag.js +254 -0
- package/lib/public/modules/split-view-presentation-drop.js +57 -0
- package/lib/public/modules/split-view-presentation-lookup.js +44 -0
- package/lib/public/modules/split-view-presentation-renderer.js +494 -0
- package/lib/public/modules/split-view-presentations.js +495 -0
- package/lib/public/modules/split-view-shell-controls.js +28 -0
- package/lib/public/modules/split-view-sidebar-menu.js +42 -0
- package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
- package/lib/public/modules/split-view-singleton.js +21 -0
- package/lib/public/modules/split-view-worker-activity.js +94 -0
- package/lib/public/modules/split-view-worker-controls.js +30 -0
- package/lib/public/modules/split-view-worker-removal.js +137 -0
- package/lib/public/modules/split-view-worker-scroll.js +57 -0
- package/lib/public/modules/split-view-worker-transcript.js +46 -0
- package/lib/public/modules/split-view-workers.js +352 -0
- package/lib/public/modules/split-view.js +61 -22
- package/lib/public/modules/sticky-notes.js +9 -2
- package/lib/public/modules/tools.js +25 -5
- package/lib/public/modules/workbench-layout-preference.js +76 -0
- package/lib/public/pane-assets/app-OJEQXBBS.js +1 -0
- package/lib/public/pane-assets/chunk-4SMIVAB4.js +1 -0
- package/lib/public/pane-assets/chunk-623V5CEO.js +1 -0
- package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
- package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
- package/lib/public/pane-assets/chunk-GML2R5JE.js +167 -0
- package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
- package/lib/public/pane-assets/chunk-I5P3MWQG.js +1 -0
- package/lib/public/pane-assets/chunk-UVGOPFEX.js +2 -0
- package/lib/public/pane-assets/manifest.json +551 -0
- package/lib/public/pane-assets/style-GN7R6XKV.css +1 -0
- package/lib/public/style.css +5 -1
- package/lib/server-owner-ui.js +6 -2
- package/lib/server-owner-view-observation.js +34 -0
- package/lib/server-palette.js +1 -0
- package/lib/server-pane-bootstrap.js +24 -0
- package/lib/server-settings.js +2 -0
- package/lib/server-static-assets.js +52 -0
- package/lib/server-workbench-layout-settings.js +56 -0
- package/lib/server.js +6 -45
- package/lib/session-activity-state.js +28 -0
- package/lib/session-list-delivery.js +17 -0
- package/lib/session-pair-turn-control.js +1 -6
- package/lib/session-search.js +3 -0
- package/lib/session-split-group-anchors.js +2 -2
- package/lib/session-split-group-roles.js +5 -8
- package/lib/session-split-group-v2-store.js +0 -3
- package/lib/sessions.js +16 -6
- package/lib/split-view-layout.js +132 -0
- package/lib/users-workbench-layout-preferences.js +33 -0
- package/lib/users.js +3 -0
- package/lib/workspace-query-service.js +1 -5
- package/lib/ws-schema.js +15 -0
- package/package.json +6 -3
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { appendWorkerRemoval, reconcileWorkerRemoval } from './split-view-worker-removal.js';
|
|
2
|
+
import { workerCursorKey, workerCursorAdvances, shouldLoadWorkerPage, captureWorkerAnchor, restoreWorkerAnchor } from './split-view-worker-scroll.js';
|
|
3
|
+
import { appendWorkerTranscript } from './split-view-worker-transcript.js';
|
|
4
|
+
import { captureWorkerDisclosures, restoreWorkerDisclosures } from './split-view-worker-activity.js';
|
|
5
|
+
// Read-only Worker conversation chips attached to a Driver's presentation pane.
|
|
6
|
+
import { store } from './store.js';
|
|
7
|
+
import { getWs } from './ws-ref.js';
|
|
8
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
9
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
10
|
+
import { splitGroupRoles } from './split-group-helpers.js';
|
|
11
|
+
import { livePaneBinding, samePaneBinding, paneTarget, samePaneTarget } from './pane-binding.js';
|
|
12
|
+
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
13
|
+
|
|
14
|
+
function sessions() { return getCachedSessions() || []; }
|
|
15
|
+
function exactDriver(origin) {
|
|
16
|
+
return sessions().find(function (session) { return session.sessionOriginId === origin && session.sessionRole === "driver"; }) || null;
|
|
17
|
+
}
|
|
18
|
+
function exactWorker(driver, origin, generation) {
|
|
19
|
+
if (!driver) return null;
|
|
20
|
+
return sessions().find(function (session) { return session.sessionOriginId === origin && session.sessionRole === "worker" &&
|
|
21
|
+
session.parentAvailable === true && session.parentSessionId === driver.id && (session.ownerId || null) === (driver.ownerId || null) &&
|
|
22
|
+
session.workerGeneration === generation; }) || null;
|
|
23
|
+
}
|
|
24
|
+
function workersFor(driver) {
|
|
25
|
+
var all = sessions().filter(function (session) { return session.sessionRole === "worker" && session.parentAvailable === true && session.parentSessionId === driver.id &&
|
|
26
|
+
(session.ownerId || null) === (driver.ownerId || null); });
|
|
27
|
+
var currentIds = new Set();
|
|
28
|
+
(store.get('splitGroups') || []).forEach(function (group) {
|
|
29
|
+
var roles = splitGroupRoles(group);
|
|
30
|
+
if (roles && roles.driverId === driver.id) roles.workerIds.forEach(function (id) { currentIds.add(id); });
|
|
31
|
+
});
|
|
32
|
+
all.sort(function (a, b) { return (currentIds.has(b.id) ? 1 : 0) - (currentIds.has(a.id) ? 1 : 0) ||
|
|
33
|
+
(Number(b.workerGeneration) || 0) - (Number(a.workerGeneration) || 0); });
|
|
34
|
+
return { items: all, currentIds: currentIds };
|
|
35
|
+
}
|
|
36
|
+
function configuredGroup(driver) {
|
|
37
|
+
if (!driver) return null;
|
|
38
|
+
var groups = store.get('splitGroups') || [];
|
|
39
|
+
for (var i = 0; i < groups.length; i++) {
|
|
40
|
+
var roles = splitGroupRoles(groups[i]);
|
|
41
|
+
if (roles && roles.version === 2 && roles.driverId === driver.id && roles.workerIds.length) return groups[i];
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function presentationSource(container, driver, connectionErrorBinding) {
|
|
46
|
+
var frame = container && container.querySelector('.split-pane-frame'), live = frame && frame.clayPane && frame.clayPane.identity();
|
|
47
|
+
// A read-only connection error may outlive the child's committed binding.
|
|
48
|
+
// Validate the recorded source against current metadata and the attached
|
|
49
|
+
// document, without granting it action authority or accepting new replies.
|
|
50
|
+
var unavailable = connectionErrorBinding && !store.get('connected') && container && !container.hidden && driver &&
|
|
51
|
+
frame && frame.clayPane && frame.clayPane.source === frame.contentWindow && frame.clayPane.document === frame.contentDocument &&
|
|
52
|
+
samePaneTarget(connectionErrorBinding, paneTarget(store.snap(), sessions(), driver.id));
|
|
53
|
+
var binding = unavailable ? connectionErrorBinding : live && live.binding;
|
|
54
|
+
if (!driver || !frame || !binding || (!unavailable && (!live || !live.ready || !livePaneBinding(frame) ||
|
|
55
|
+
live.sessionId !== driver.id || live.ownerId !== (store.get('myUserId') || null) || live.projectSlug !== store.get('currentSlug'))) ||
|
|
56
|
+
frame.dataset.sessionId !== String(driver.id) || frame.dataset.sessionOriginId !== driver.sessionOriginId ||
|
|
57
|
+
binding.sessionOriginId !== driver.sessionOriginId) return null;
|
|
58
|
+
var view = (store.get('projectSplitViews') || []).find(function (item) { return item.id === store.get('projectSplitViewActiveId'); });
|
|
59
|
+
var inView = !!(view && view.sessions.some(function (session) { return session.id === driver.id && session.sessionOriginId === driver.sessionOriginId; }));
|
|
60
|
+
var inSingleton = !view && store.get('projectSplitViewSingletonOrigin') === driver.sessionOriginId;
|
|
61
|
+
return inView || inSingleton ? { frame: frame, live: live, binding: binding, view: view, singleton: inSingleton } : null;
|
|
62
|
+
}
|
|
63
|
+
export function workerRemovalTarget(container, driverOrigin, workerOrigin, generation) {
|
|
64
|
+
var driver = exactDriver(driverOrigin), worker = exactWorker(driver, workerOrigin, generation);
|
|
65
|
+
var source = presentationSource(container, driver);
|
|
66
|
+
if (!worker || worker.hidden || !driver || driver.hidden || (driver.ownerId || null) !== (store.get('myUserId') || null) ||
|
|
67
|
+
document.hidden || !source || !store.get('connected') || !container.isConnected || container.hidden || container.inert ||
|
|
68
|
+
source.frame.hidden || source.frame.inert || store.get('permissions') && store.get('permissions').sessionDelete === false) return null;
|
|
69
|
+
return { projectSlug: store.get('currentSlug'), ownerId: store.get('myUserId') || null, driverId: driver.id,
|
|
70
|
+
driverOriginId: driverOrigin, workerId: worker.id, workerOriginId: workerOrigin, generation: generation,
|
|
71
|
+
bindingGeneration: source.binding.generation, bindingRequestId: source.binding.requestId,
|
|
72
|
+
viewRevision: store.get('projectSplitViewRevision'), socketGeneration: store.get('projectSplitViewSocketGeneration') };
|
|
73
|
+
}
|
|
74
|
+
function popover() { return document.getElementById("split-view-worker-popover"); }
|
|
75
|
+
function close(returnFocus) {
|
|
76
|
+
var state = store.get('splitViewWorkerConversation');
|
|
77
|
+
if (!state) return;
|
|
78
|
+
if (state.fillTimer) clearTimeout(state.fillTimer);
|
|
79
|
+
if (state.requestTimer) clearTimeout(state.requestTimer);
|
|
80
|
+
store.set({ splitViewWorkerConversation: null });
|
|
81
|
+
var element = popover(); if (element) element.remove();
|
|
82
|
+
if (returnFocus) {
|
|
83
|
+
var chip = document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"] [data-worker-origin="' + CSS.escape(state.workerOriginId) + '"]');
|
|
84
|
+
if (chip && chip.isConnected && !chip.closest('.split-pane').hidden) chip.focus();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function position(element, anchor) {
|
|
88
|
+
if (!element || !anchor || !anchor.isConnected) return;
|
|
89
|
+
var rect = anchor.getBoundingClientRect(), width = Math.min(430, window.innerWidth - 20), height = Math.min(440, window.innerHeight - 24);
|
|
90
|
+
element.style.width = width + "px"; element.style.height = height + "px";
|
|
91
|
+
element.style.left = Math.max(10, Math.min(rect.left, window.innerWidth - width - 10)) + "px";
|
|
92
|
+
element.style.top = Math.max(10, Math.min(rect.bottom + 6, window.innerHeight - height - 10)) + "px";
|
|
93
|
+
}
|
|
94
|
+
function requestPage(state, before, append) {
|
|
95
|
+
var current = store.get('splitViewWorkerConversation');
|
|
96
|
+
if (!state || !current || state.requestId !== current.requestId || current.inFlight || workerCursorKey(before) === null || !ensurePopover()) return;
|
|
97
|
+
state = store.get('splitViewWorkerConversation');
|
|
98
|
+
if (append && workerCursorKey(before) !== workerCursorKey(state.nextBefore)) return;
|
|
99
|
+
if (state.fillTimer) clearTimeout(state.fillTimer);
|
|
100
|
+
var ws = getWs();
|
|
101
|
+
if (!store.get('connected') || !ws || ws.readyState !== 1) {
|
|
102
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, state, { status: "error", error: "Worker conversation is unavailable while disconnected.", append: false }) });
|
|
103
|
+
renderContent(); return;
|
|
104
|
+
}
|
|
105
|
+
var requestId = "worker-read-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 9);
|
|
106
|
+
var pending = Object.assign({}, state, { requestId: requestId, socket: ws, projectSlug: store.get('currentSlug'), ownerId: store.get('myUserId') || null,
|
|
107
|
+
socketGeneration: store.get('projectSplitViewSocketGeneration') || 0, status: "loading", error: null, inFlight: true, fillTimer: null, requestedBefore: before == null ? null : before, append: append === true });
|
|
108
|
+
pending.requestTimer = setTimeout(function () {
|
|
109
|
+
var current = store.get('splitViewWorkerConversation');
|
|
110
|
+
if (!current || current.requestId !== requestId || !current.inFlight) return;
|
|
111
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, current, { status: 'error', inFlight: false, append: false,
|
|
112
|
+
requestTimer: null, error: 'Worker conversation request timed out. Scroll up or retry to load it again.' }) });
|
|
113
|
+
renderContent();
|
|
114
|
+
}, 15000);
|
|
115
|
+
store.set({ splitViewWorkerConversation: pending }); renderContent();
|
|
116
|
+
try {
|
|
117
|
+
ws.send(JSON.stringify({ type: "split_worker_conversation_request", requestId: requestId,
|
|
118
|
+
parentSessionOriginId: state.driverOriginId, workerSessionOriginId: state.workerOriginId,
|
|
119
|
+
generation: state.generation, before: before }));
|
|
120
|
+
} catch (_error) {
|
|
121
|
+
var current = store.get('splitViewWorkerConversation');
|
|
122
|
+
if (current && current.requestId === requestId) {
|
|
123
|
+
clearTimeout(current.requestTimer);
|
|
124
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, current, { status: "error", error: "Worker conversation request could not be sent.", inFlight: false, append: false }) });
|
|
125
|
+
renderContent();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function open(chip, driver, worker) {
|
|
130
|
+
var existing = store.get('splitViewWorkerConversation');
|
|
131
|
+
if (existing && existing.driverOriginId === driver.sessionOriginId && existing.workerOriginId === worker.sessionOriginId && existing.generation === worker.workerGeneration) { close(true); return; }
|
|
132
|
+
if (existing) close(false);
|
|
133
|
+
var state = { requestId: null, driverOriginId: driver.sessionOriginId, workerOriginId: worker.sessionOriginId,
|
|
134
|
+
generation: worker.workerGeneration, driverTitle: driver.title || "Driver session", workerTitle: worker.title || "Worker",
|
|
135
|
+
sourceBinding: chip.closest('.split-pane').querySelector('.split-pane-frame').clayPane.identity().binding,
|
|
136
|
+
status: "loading", messages: [], hasMore: false, nextBefore: null, append: false, inFlight: false, initialPage: true };
|
|
137
|
+
store.set({ splitViewWorkerConversation: state }); ensurePopover(); renderContent();
|
|
138
|
+
var element = popover(); position(element, chip);
|
|
139
|
+
requestPage(state, undefined, false);
|
|
140
|
+
var closeButton = element && element.querySelector('[aria-label="Close Worker conversation"]'); if (closeButton) closeButton.focus();
|
|
141
|
+
}
|
|
142
|
+
function ensurePopover() {
|
|
143
|
+
var state = store.get('splitViewWorkerConversation');
|
|
144
|
+
var element = popover();
|
|
145
|
+
if (!state) { if (element) element.remove(); return; }
|
|
146
|
+
var driver = exactDriver(state.driverOriginId), worker = exactWorker(driver, state.workerOriginId, state.generation);
|
|
147
|
+
var sourcePane = document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"]');
|
|
148
|
+
var source = presentationSource(sourcePane, driver, state.status === 'error' && state.sourceBinding);
|
|
149
|
+
if (!driver || !worker || !source || sourcePane.hidden || !state.sourceBinding || !samePaneBinding(source.binding || source.live && source.live.binding, state.sourceBinding) ||
|
|
150
|
+
source.view && store.get('projectSplitViewFocusedOrigin') && store.get('projectSplitViewFocusedOrigin') !== state.driverOriginId) {
|
|
151
|
+
close(false); return;
|
|
152
|
+
}
|
|
153
|
+
if (!element) {
|
|
154
|
+
element = document.createElement("section"); element.id = "split-view-worker-popover"; element.className = "split-view-worker-popover";
|
|
155
|
+
element.setAttribute("role", "dialog"); element.setAttribute("aria-label", "Read-only Worker conversation"); document.body.appendChild(element);
|
|
156
|
+
var header = document.createElement("header"); header.className = "split-view-worker-popover-header";
|
|
157
|
+
var title = document.createElement("strong"); title.className = "worker-popover-title"; header.appendChild(title);
|
|
158
|
+
var closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.setAttribute("aria-label", "Close Worker conversation"); closeButton.innerHTML = iconHtml("x");
|
|
159
|
+
closeButton.addEventListener("click", function () { close(true); }); header.appendChild(closeButton); element.appendChild(header);
|
|
160
|
+
var source = document.createElement("div"); source.className = "split-view-worker-popover-source"; element.appendChild(source);
|
|
161
|
+
var body = document.createElement("div"); body.className = "split-view-worker-popover-body"; body.tabIndex = 0; body.setAttribute("aria-label", "Read-only Worker transcript"); element.appendChild(body);
|
|
162
|
+
body.addEventListener('scroll', function () { loadEarlier(body, true); }, { passive: true });
|
|
163
|
+
body.addEventListener('wheel', function (event) { if (event.deltaY < 0) loadEarlier(body, true); }, { passive: true });
|
|
164
|
+
var footer = document.createElement("footer"); footer.className = "split-view-worker-popover-footer"; element.appendChild(footer);
|
|
165
|
+
}
|
|
166
|
+
return element;
|
|
167
|
+
}
|
|
168
|
+
function renderContent() {
|
|
169
|
+
var state = store.get('splitViewWorkerConversation'), element = ensurePopover();
|
|
170
|
+
if (!state || !element) return;
|
|
171
|
+
var title = element.querySelector('.worker-popover-title'), source = element.querySelector('.split-view-worker-popover-source');
|
|
172
|
+
var body = element.querySelector('.split-view-worker-popover-body'), footer = element.querySelector('.split-view-worker-popover-footer');
|
|
173
|
+
if (title) title.textContent = state.workerTitle;
|
|
174
|
+
if (source) source.textContent = state.driverTitle + " · " + state.workerTitle + " · Gen " + state.generation;
|
|
175
|
+
var footerFocused = footer && footer.contains(document.activeElement);
|
|
176
|
+
if (body && (state.renderedMessages !== state.messages || !state.messages.length)) {
|
|
177
|
+
var anchor = state.prepend ? captureWorkerAnchor(body) : null;
|
|
178
|
+
var disclosures = captureWorkerDisclosures(body);
|
|
179
|
+
var oldTop = body.scrollTop, wasFocused = document.activeElement === body;
|
|
180
|
+
body.textContent = "";
|
|
181
|
+
if (state.status === "loading" && !state.messages.length) body.textContent = "Loading conversation…";
|
|
182
|
+
else if (state.status === "error" && !state.messages.length) body.textContent = state.error || "Conversation is unavailable.";
|
|
183
|
+
else if (!state.messages.length) body.textContent = "No recorded conversation.";
|
|
184
|
+
appendWorkerTranscript(body, state.messages);
|
|
185
|
+
restoreWorkerDisclosures(body, disclosures);
|
|
186
|
+
if (state.initialPage && state.status === 'ready') body.scrollTop = body.scrollHeight;
|
|
187
|
+
else if (anchor) restoreWorkerAnchor(body, anchor);
|
|
188
|
+
else body.scrollTop = oldTop;
|
|
189
|
+
if (wasFocused) body.focus({ preventScroll: true });
|
|
190
|
+
}
|
|
191
|
+
if (body) store.set({ splitViewWorkerConversation: Object.assign({}, state, { renderedMessages: state.messages, prepend: false,
|
|
192
|
+
initialPage: state.status === 'ready' ? false : state.initialPage }) });
|
|
193
|
+
if (footer) {
|
|
194
|
+
footer.textContent = "Read-only conversation";
|
|
195
|
+
if (state.status === "error") { var status = document.createElement('span'); status.setAttribute('role', 'status'); status.textContent = state.error || "Conversation is unavailable."; footer.appendChild(status); }
|
|
196
|
+
var status = document.createElement('span'); status.className = 'split-view-worker-page-status'; status.setAttribute('role', 'status');
|
|
197
|
+
status.textContent = state.inFlight ? (state.messages.length ? 'Loading earlier messages…' : 'Loading conversation…') : state.hasMore ? 'Scroll up for earlier messages' : state.messages.length ? 'Beginning of conversation' : '';
|
|
198
|
+
footer.appendChild(status);
|
|
199
|
+
if (state.status === 'error') {
|
|
200
|
+
var retry = document.createElement('button'); retry.type = 'button'; retry.textContent = 'Retry loading conversation';
|
|
201
|
+
retry.addEventListener('click', function () { var current = store.get('splitViewWorkerConversation'); if (current) requestPage(current, current.requestedBefore, current.messages.length > 0); });
|
|
202
|
+
footer.appendChild(retry);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (footerFocused) { var nextFocus = footer.querySelector('button') || body; if (nextFocus) nextFocus.focus({ preventScroll: true }); }
|
|
206
|
+
refreshIcons();
|
|
207
|
+
scheduleWorkerFill(body);
|
|
208
|
+
}
|
|
209
|
+
function loadEarlier(body, intentional) {
|
|
210
|
+
var state = store.get('splitViewWorkerConversation');
|
|
211
|
+
if (!shouldLoadWorkerPage(state, body, intentional)) return;
|
|
212
|
+
requestPage(state, state.nextBefore, true);
|
|
213
|
+
}
|
|
214
|
+
function scheduleWorkerFill(body) {
|
|
215
|
+
var state = store.get('splitViewWorkerConversation');
|
|
216
|
+
if (!shouldLoadWorkerPage(state, body, false) || state.fillTimer) return;
|
|
217
|
+
var requestId = state.requestId;
|
|
218
|
+
var timer = setTimeout(function () {
|
|
219
|
+
var current = store.get('splitViewWorkerConversation');
|
|
220
|
+
if (!current || current.requestId !== requestId || current.fillTimer !== timer || !body.isConnected) return;
|
|
221
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, current, { fillTimer: null }) });
|
|
222
|
+
loadEarlier(body, false);
|
|
223
|
+
}, 120);
|
|
224
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, state, { fillTimer: timer }) });
|
|
225
|
+
}
|
|
226
|
+
function anchorChip(state) {
|
|
227
|
+
if (!state) return null;
|
|
228
|
+
return document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"] [data-worker-origin="' + CSS.escape(state.workerOriginId) + '"]');
|
|
229
|
+
}
|
|
230
|
+
function reanchorPopover() {
|
|
231
|
+
var state = store.get('splitViewWorkerConversation');
|
|
232
|
+
if (state) position(popover(), anchorChip(state));
|
|
233
|
+
}
|
|
234
|
+
export function handleSplitWorkerConversation(message) {
|
|
235
|
+
var state = store.get('splitViewWorkerConversation');
|
|
236
|
+
if (!state || !store.get('connected') || !message || message.type !== "split_worker_conversation" || state.socket !== getWs() || state.projectSlug !== store.get('currentSlug') ||
|
|
237
|
+
state.ownerId !== (store.get('myUserId') || null) || state.socketGeneration !== (store.get('projectSplitViewSocketGeneration') || 0) || message.requestId !== state.requestId ||
|
|
238
|
+
message.parentSessionOriginId !== state.driverOriginId || message.workerSessionOriginId !== state.workerOriginId || message.generation !== state.generation) return false;
|
|
239
|
+
var driver = exactDriver(state.driverOriginId), sourcePane = document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"]');
|
|
240
|
+
var source = presentationSource(sourcePane, driver);
|
|
241
|
+
if (!exactWorker(driver, state.workerOriginId, state.generation) || !source || !state.sourceBinding || !samePaneBinding(source.live.binding, state.sourceBinding)) { close(false); return true; }
|
|
242
|
+
if (!state.inFlight || state.status !== 'loading' || !Object.prototype.hasOwnProperty.call(message, 'pageBefore') || workerCursorKey(message.pageBefore) !== workerCursorKey(state.requestedBefore)) return false;
|
|
243
|
+
clearTimeout(state.requestTimer);
|
|
244
|
+
var error = message.error || null;
|
|
245
|
+
if (!error && (!Array.isArray(message.messages) || message.hasMore && !workerCursorAdvances(state.requestedBefore, message.nextBefore))) error = 'Worker history page did not advance. Retry loading conversation.';
|
|
246
|
+
var incoming = error ? [] : message.messages;
|
|
247
|
+
var messages = error ? state.messages : state.append ? incoming.concat(state.messages || []) : incoming.slice();
|
|
248
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, state, { status: error ? 'error' : 'ready', error: error,
|
|
249
|
+
messages: messages, inFlight: false, requestTimer: null, hasMore: error ? state.hasMore : message.hasMore === true,
|
|
250
|
+
nextBefore: error ? state.nextBefore : message.nextBefore, prepend: !error && state.append, append: false }) });
|
|
251
|
+
renderContent(); return true;
|
|
252
|
+
}
|
|
253
|
+
export function renderPaneWorkers(container, driver) {
|
|
254
|
+
if (!container || !driver) return;
|
|
255
|
+
reconcileWorkerRemoval(container);
|
|
256
|
+
var root = container.querySelector('.split-view-workers');
|
|
257
|
+
var data = workersFor(driver);
|
|
258
|
+
if (!root) { root = document.createElement("div"); root.className = "split-view-workers"; root.setAttribute("aria-label", "Worker conversations");
|
|
259
|
+
var frame = container.querySelector('.split-pane-frame'); if (frame) container.insertBefore(root, frame); else container.appendChild(root); }
|
|
260
|
+
var source = presentationSource(container, driver);
|
|
261
|
+
root.hidden = !source || data.items.length === 0;
|
|
262
|
+
if (!source) { if (store.get('splitViewWorkerConversation') && store.get('splitViewWorkerConversation').driverOriginId === driver.sessionOriginId) ensurePopover(); return; }
|
|
263
|
+
root.textContent = "";
|
|
264
|
+
data.items.forEach(function (worker) {
|
|
265
|
+
var pill = document.createElement("span"); pill.className = "split-view-worker-pill";
|
|
266
|
+
var button = document.createElement("button"); button.type = "button"; button.className = "split-view-worker-chip";
|
|
267
|
+
button.dataset.workerOrigin = worker.sessionOriginId; button.dataset.workerGeneration = String(worker.workerGeneration);
|
|
268
|
+
var current = data.currentIds.has(worker.id);
|
|
269
|
+
var modelLabel = worker.model || "Default model";
|
|
270
|
+
button.title = (VENDOR_NAMES[worker.vendor] || worker.vendor || "Unknown vendor") + " · " + modelLabel + " · Split Worker #" + worker.id +
|
|
271
|
+
(current ? "" : " · Archived · Gen " + worker.workerGeneration) + " · " + (worker.title || "Worker");
|
|
272
|
+
button.setAttribute("aria-label", button.title + " conversation");
|
|
273
|
+
var dot = document.createElement("span"); dot.className = "split-view-worker-dot"; dot.dataset.state = current && worker.isProcessing ? "working" : "ready";
|
|
274
|
+
if (VENDOR_AVATARS[worker.vendor]) {
|
|
275
|
+
var vendorIcon = document.createElement("img"); vendorIcon.className = "split-view-worker-vendor";
|
|
276
|
+
vendorIcon.src = VENDOR_AVATARS[worker.vendor]; vendorIcon.alt = ""; vendorIcon.setAttribute("aria-hidden", "true"); button.appendChild(vendorIcon);
|
|
277
|
+
}
|
|
278
|
+
var name = document.createElement("span"); name.className = "split-view-worker-name"; name.textContent = modelLabel; button.appendChild(name);
|
|
279
|
+
var identity = document.createElement("span"); identity.className = "split-view-worker-identity";
|
|
280
|
+
identity.textContent = "Split Worker #" + worker.id; button.appendChild(identity);
|
|
281
|
+
if (!current) {
|
|
282
|
+
var generation = document.createElement("span"); generation.className = "split-view-worker-generation";
|
|
283
|
+
generation.textContent = "Gen " + worker.workerGeneration; button.appendChild(generation);
|
|
284
|
+
}
|
|
285
|
+
button.appendChild(dot);
|
|
286
|
+
var state = store.get('splitViewWorkerConversation');
|
|
287
|
+
button.setAttribute("aria-expanded", state && state.driverOriginId === driver.sessionOriginId && state.workerOriginId === worker.sessionOriginId ? "true" : "false");
|
|
288
|
+
button.addEventListener("click", function () { if (presentationSource(container, exactDriver(driver.sessionOriginId))) open(button, driver, worker); }); pill.appendChild(button); root.appendChild(pill);
|
|
289
|
+
appendWorkerRemoval(pill, button, container, driver, worker);
|
|
290
|
+
});
|
|
291
|
+
var activeView = (store.get('projectSplitViews') || []).find(function (view) { return view.id === store.get('projectSplitViewActiveId'); });
|
|
292
|
+
var group = configuredGroup(driver);
|
|
293
|
+
if ((activeView && activeView.sessions.some(function (session) { return session.sessionOriginId === driver.sessionOriginId; }) || source.singleton) && group) {
|
|
294
|
+
var controls = document.createElement('button'); controls.type = 'button'; controls.className = 'split-view-worker-controls';
|
|
295
|
+
controls.textContent = 'Controls'; controls.setAttribute('aria-label', 'Open Worker controls for ' + (driver.title || 'Driver session'));
|
|
296
|
+
controls.dataset.driverOrigin = driver.sessionOriginId; controls.dataset.groupId = String(group.id);
|
|
297
|
+
controls.addEventListener('click', function (event) {
|
|
298
|
+
event.preventDefault(); event.stopPropagation();
|
|
299
|
+
var frame = container.querySelector('.split-pane-frame'), live = frame && frame.clayPane && frame.clayPane.identity();
|
|
300
|
+
var currentSource = presentationSource(container, exactDriver(driver.sessionOriginId));
|
|
301
|
+
if (!frame || !live || !live.ready || !currentSource || !frame.isConnected || frame.dataset.sessionOriginId !== driver.sessionOriginId ||
|
|
302
|
+
Number(frame.dataset.sessionId) !== driver.id || live.sessionId !== driver.id || live.binding.sessionOriginId !== driver.sessionOriginId ||
|
|
303
|
+
frame.dataset.projectSlug !== store.get('currentSlug') || configuredGroup(exactDriver(driver.sessionOriginId)) !== group) return;
|
|
304
|
+
document.dispatchEvent(new CustomEvent('clay-split-view-open-worker-controls', { detail: {
|
|
305
|
+
driverOriginId: driver.sessionOriginId, driverId: driver.id, groupId: group.id, projectSlug: store.get('currentSlug'),
|
|
306
|
+
ownerId: driver.ownerId || null, workerOrigins: group.members.map(function (id) {
|
|
307
|
+
var member = sessions().find(function (session) { return session.id === id; }); return member && member.sessionRole === 'worker' ? member.sessionOriginId : null;
|
|
308
|
+
})
|
|
309
|
+
} }));
|
|
310
|
+
});
|
|
311
|
+
root.appendChild(controls);
|
|
312
|
+
}
|
|
313
|
+
var conversation = store.get('splitViewWorkerConversation');
|
|
314
|
+
if (conversation) ensurePopover();
|
|
315
|
+
}
|
|
316
|
+
document.addEventListener("keydown", function (event) { if (event.key === "Escape" && store.get('splitViewWorkerConversation')) { event.preventDefault(); event.stopImmediatePropagation(); close(true); } }, true);
|
|
317
|
+
document.addEventListener("click", function (event) {
|
|
318
|
+
if (!store.get('splitViewWorkerConversation')) return;
|
|
319
|
+
var element = popover(); if (element && element.contains(event.target)) return;
|
|
320
|
+
if (event.target.closest && event.target.closest('.split-view-worker-chip')) return;
|
|
321
|
+
var interactive = event.target.closest && event.target.closest('button, a, input, textarea, select, [contenteditable="true"], [tabindex]:not([tabindex="-1"])');
|
|
322
|
+
close(!interactive);
|
|
323
|
+
}, true);
|
|
324
|
+
window.addEventListener("resize", reanchorPopover);
|
|
325
|
+
window.addEventListener("scroll", function (event) {
|
|
326
|
+
var element = popover();
|
|
327
|
+
if (element && event.target && (event.target === element || event.target.nodeType && element.contains(event.target))) return;
|
|
328
|
+
reanchorPopover();
|
|
329
|
+
}, true);
|
|
330
|
+
store.subscribe(function (state, previous) {
|
|
331
|
+
if (state.splitViewWorkerConversation && (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId ||
|
|
332
|
+
state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration)) {
|
|
333
|
+
close(false);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (state.splitViewWorkerConversation && (state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.projectSplitViews !== previous.projectSplitViews ||
|
|
337
|
+
state.projectSplitViewSingletonOrigin !== previous.projectSplitViewSingletonOrigin || state.splitPanes !== previous.splitPanes ||
|
|
338
|
+
state.projectSplitViewFocusedOrigin !== previous.projectSplitViewFocusedOrigin || state.paneBindingSessions !== previous.paneBindingSessions)) {
|
|
339
|
+
if (state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.projectSplitViewFocusedOrigin !== previous.projectSplitViewFocusedOrigin ||
|
|
340
|
+
state.paneBindingSessions !== previous.paneBindingSessions) ensurePopover();
|
|
341
|
+
else renderContent();
|
|
342
|
+
}
|
|
343
|
+
if (state.connected !== previous.connected && state.connected === false) {
|
|
344
|
+
var conversation = state.splitViewWorkerConversation;
|
|
345
|
+
if (conversation) {
|
|
346
|
+
if (conversation.requestTimer) clearTimeout(conversation.requestTimer);
|
|
347
|
+
if (conversation.fillTimer) clearTimeout(conversation.fillTimer);
|
|
348
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, conversation, { status: "error", error: "Worker conversation connection was lost.", inFlight: false, append: false }) });
|
|
349
|
+
renderContent();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
});
|
|
@@ -22,8 +22,13 @@ import { handlePaneIssueMessage } from './pane-issue-bridge.js';
|
|
|
22
22
|
import { handlePaneFileMessage, revealFilesPanel } from './pane-file-bridge.js';
|
|
23
23
|
import { openFile } from './filebrowser.js';
|
|
24
24
|
import { restoreSplitGroup, reconcileSplitIdentity } from './split-group-restore.js';
|
|
25
|
+
import { clearRetainedPanes, releaseRetainedPanes, disposePaneChildren, preserveReconnectingPresentation } from './split-pane-retention.js';
|
|
25
26
|
import { reconcileSplitPanes, splitPaneForSession } from './split-pane-reconciler.js';
|
|
26
|
-
import { createSplitPane, updateSplitPaneMetadata, updatePaneContextChip, updatePaneFullAccessButton } from './split-pane-renderer.js';
|
|
27
|
+
import { createSplitPane, updateSplitPaneMetadata, updatePaneContextChip, updatePaneFullAccessButton, handlePaneSearchMessage } from './split-pane-renderer.js';
|
|
28
|
+
import { initSplitViewPresentations, renderPresentationSplitView } from './split-view-presentations.js';
|
|
29
|
+
import { currentSingletonPresentation } from './split-view-singleton.js';
|
|
30
|
+
import { initSplitViewWorkerControls } from './split-view-worker-controls.js';
|
|
31
|
+
import { createSidebarReopenButton, syncSidebarReopenButton } from './split-view-shell-controls.js';
|
|
27
32
|
|
|
28
33
|
var host = null;
|
|
29
34
|
var nativeApp = null;
|
|
@@ -34,6 +39,7 @@ var draggedSessionId = null;
|
|
|
34
39
|
var stickyNotesContainer = null;
|
|
35
40
|
var stickyNotesHome = null;
|
|
36
41
|
var stickyNotesAnchor = null;
|
|
42
|
+
var shellSidebarExpand = null;
|
|
37
43
|
function placeStickyNotesOverlay(splitActive) {
|
|
38
44
|
if (!stickyNotesContainer || !stickyNotesHome || !mainPanelsEl) return;
|
|
39
45
|
if (splitActive) {
|
|
@@ -164,6 +170,10 @@ function handlePaneMessage(event) {
|
|
|
164
170
|
if (handlePaneIssueMessage(event, host)) return;
|
|
165
171
|
if (handlePaneBrowserMessage(event, host)) return;
|
|
166
172
|
if (handlePaneFileMessage(event, host, openFile, revealFilesPanel)) return;
|
|
173
|
+
if (msg.type === "clay-pane-search-status" || msg.type === "clay-pane-search-open") {
|
|
174
|
+
handlePaneSearchMessage(event, host);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
167
177
|
if (msg.type !== "clay-pane-context") return;
|
|
168
178
|
var frames = host.querySelectorAll(".split-pane-frame");
|
|
169
179
|
for (var i = 0; i < frames.length; i++) {
|
|
@@ -175,18 +185,37 @@ function handlePaneMessage(event) {
|
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
function renderSplit(split) {
|
|
178
|
-
if (!host || !nativeApp) return;
|
|
188
|
+
if (!host || !nativeApp || preserveReconnectingPresentation(host)) return;
|
|
189
|
+
var state = store.snap(), singleton = !split && !state.splitPanes && !state.projectSplitViewActiveId && currentSingletonPresentation(state, getCachedSessions());
|
|
190
|
+
syncSidebarReopenButton(shellSidebarExpand,
|
|
191
|
+
singleton || split && split.panes && split.panes.length >= 2 || !!state.projectSplitViewActiveId);
|
|
192
|
+
// The exact singleton presentation is verified before the redundant cache catches up.
|
|
193
|
+
if (singleton) { renderPresentationSplitView(); return; }
|
|
194
|
+
if (split && split.panes && store.get('projectSplitViewActiveId')) {
|
|
195
|
+
store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null });
|
|
196
|
+
}
|
|
197
|
+
var expectedPresentationContext = [store.get('currentSlug') || "", store.get('currentProjectOwnerId') || "", store.get('myUserId') || "", store.get('projectSplitViewSocketGeneration') || 0].join("|");
|
|
198
|
+
if (!(split && split.panes) && store.get('projectSplitViewActiveId') && store.get('projectSplitViewContextKey') === expectedPresentationContext) {
|
|
199
|
+
renderPresentationSplitView();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (split && split.panes) releaseRetainedPanes(host);
|
|
203
|
+
else clearRetainedPanes(host);
|
|
179
204
|
var panes = split && split.panes;
|
|
180
|
-
if (!panes || panes.length < 2
|
|
205
|
+
if (!panes || panes.length < 2) {
|
|
206
|
+
if (shellSidebarExpand) shellSidebarExpand.hidden = true;
|
|
181
207
|
placeStickyNotesOverlay(false);
|
|
182
208
|
var frames = host.querySelectorAll('.split-pane-frame');
|
|
183
209
|
var retain = frames.length > 0 && frames.length <= 3 && Array.from(frames).every(function (frame) { return frame.dataset.projectSlug === store.get('currentSlug') && frame.clayPane; });
|
|
184
210
|
if (retain) Array.from(frames).forEach(function (frame) { frame.clayPane.suspend(); });
|
|
185
|
-
else host.innerHTML = "";
|
|
186
|
-
host.classList.remove("visible");
|
|
211
|
+
else { disposePaneChildren(host); host.innerHTML = ""; }
|
|
212
|
+
host.classList.remove("visible", "split-view-canvas");
|
|
213
|
+
delete host.dataset.singlePane;
|
|
214
|
+
delete host.dataset.positioned;
|
|
187
215
|
nativeApp.classList.remove("split-native-hidden");
|
|
188
216
|
return;
|
|
189
217
|
}
|
|
218
|
+
if (shellSidebarExpand) shellSidebarExpand.hidden = !document.getElementById("layout").classList.contains("sidebar-collapsed");
|
|
190
219
|
placeStickyNotesOverlay(true);
|
|
191
220
|
nativeApp.classList.add("split-native-hidden");
|
|
192
221
|
host.classList.add("visible");
|
|
@@ -223,7 +252,7 @@ function openSplit(side, draggedId) {
|
|
|
223
252
|
|
|
224
253
|
export function openGroup(group) {
|
|
225
254
|
if (!canLeavePanes()) return false;
|
|
226
|
-
if (!group || !Array.isArray(group.members) || group.members.length < 2
|
|
255
|
+
if (!group || !Array.isArray(group.members) || group.members.length < 2) return false;
|
|
227
256
|
var memberIds = splitGroupMemberIds(group);
|
|
228
257
|
if (memberIds.length !== group.members.length) return false;
|
|
229
258
|
for (var mi = 0; mi < memberIds.length; mi++) if (!sessionById(memberIds[mi])) return false;
|
|
@@ -273,7 +302,7 @@ export function openWorkerWithDriver(worker) {
|
|
|
273
302
|
|
|
274
303
|
// Keep the public entry shared by connection, sidebar and identity hydration.
|
|
275
304
|
export function maybeRestoreSplitGroup() {
|
|
276
|
-
restoreSplitGroup(
|
|
305
|
+
restoreSplitGroup();
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
export function separateGroup(group) {
|
|
@@ -326,7 +355,8 @@ function createDropOverlay(mainPanels) {
|
|
|
326
355
|
}
|
|
327
356
|
|
|
328
357
|
function handleSessionDragStart(event) {
|
|
329
|
-
if (store.get('
|
|
358
|
+
if (event.defaultPrevented || store.get('projectSplitViewDrag') || store.get('projectSplitViewSingletonOrigin') ||
|
|
359
|
+
store.get('projectSplitViewActiveId') || store.get('splitPanes')) return;
|
|
330
360
|
var item = event.target.closest("[data-session-id][draggable='true']");
|
|
331
361
|
if (!item) return;
|
|
332
362
|
draggedSessionId = parseInt(item.dataset.sessionId, 10);
|
|
@@ -337,26 +367,27 @@ function handleSessionDragStart(event) {
|
|
|
337
367
|
if (draggedSessionId) showDropOverlay();
|
|
338
368
|
}
|
|
339
369
|
|
|
340
|
-
function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
370
|
+
function keepCurrentDriverGroup(driver) {
|
|
371
|
+
if (!isCurrentProjectSessionReady(store.snap(), getWs())) return false;
|
|
372
|
+
var split = store.get('splitPanes');
|
|
373
|
+
if (!driver || driver.sessionRole !== "driver" || !split || !split.groupId) return false;
|
|
374
|
+
var groups = store.get('splitGroups') || [];
|
|
344
375
|
for (var i = 0; i < groups.length; i++) {
|
|
345
|
-
var group = groups[i];
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
var available = roles.workerIds.every(function (id) {
|
|
376
|
+
var group = groups[i], roles = splitGroupRoles(group);
|
|
377
|
+
if (!roles || group.id !== split.groupId || roles.driverId !== driver.id || typeof group.id !== "string" || !group.id) continue;
|
|
378
|
+
var valid = roles.workerIds.every(function (id) {
|
|
349
379
|
var worker = sessionById(id);
|
|
350
|
-
return worker && worker.sessionRole === "worker" && worker.parentAvailable === true &&
|
|
380
|
+
return worker && worker.sessionRole === "worker" && worker.parentAvailable === true &&
|
|
381
|
+
worker.parentSessionId === driver.id && (worker.ownerId || null) === (driver.ownerId || null);
|
|
351
382
|
});
|
|
352
|
-
|
|
353
|
-
|
|
383
|
+
if (!valid) return false;
|
|
384
|
+
return openGroup(group);
|
|
354
385
|
}
|
|
355
386
|
return false;
|
|
356
387
|
}
|
|
357
388
|
|
|
358
389
|
function handleSidebarSessionClick(event) {
|
|
359
|
-
if (!
|
|
390
|
+
var state = store.snap(); if ((!state.splitPanes && !currentSingletonPresentation(state, getCachedSessions())) || event.button !== 0) return;
|
|
360
391
|
// This document capture listener runs before a row's bubble handlers. Leave
|
|
361
392
|
// real controls alone so GitHub anchors retain native click, modified-click,
|
|
362
393
|
// and keyboard navigation rather than selecting their owning session.
|
|
@@ -370,7 +401,7 @@ function handleSidebarSessionClick(event) {
|
|
|
370
401
|
if (!canLeavePanes()) return;
|
|
371
402
|
var session = sessionById(sessionId);
|
|
372
403
|
if (session && session.sessionRole === "worker" && openWorkerWithDriver(session)) return;
|
|
373
|
-
if (
|
|
404
|
+
if (keepCurrentDriverGroup(session)) return;
|
|
374
405
|
switchNativeSession(sessionId);
|
|
375
406
|
}
|
|
376
407
|
|
|
@@ -398,9 +429,14 @@ export function initSplitView() {
|
|
|
398
429
|
host = document.createElement("div");
|
|
399
430
|
host.id = "split-host";
|
|
400
431
|
mainPanels.insertBefore(host, nativeApp.nextSibling);
|
|
432
|
+
shellSidebarExpand = createSidebarReopenButton();
|
|
433
|
+
window.addEventListener("resize", function () {
|
|
434
|
+
syncSidebarReopenButton(shellSidebarExpand, !!host && host.classList.contains("visible"));
|
|
435
|
+
});
|
|
401
436
|
dropOverlay = createDropOverlay(mainPanels);
|
|
402
437
|
|
|
403
438
|
window.addEventListener("message", handlePaneMessage);
|
|
439
|
+
initSplitViewWorkerControls(openGroup);
|
|
404
440
|
document.addEventListener("dragstart", handleSessionDragStart);
|
|
405
441
|
document.addEventListener("dragend", hideDropOverlay);
|
|
406
442
|
document.addEventListener("drop", function (event) {
|
|
@@ -411,6 +447,8 @@ export function initSplitView() {
|
|
|
411
447
|
if (state.authIdentityLoaded && !prev.authIdentityLoaded) queueMicrotask(maybeRestoreSplitGroup);
|
|
412
448
|
if (reconcileSplitIdentity(state, prev, host)) { renderSplit(null); return; }
|
|
413
449
|
if (state.splitPanes !== prev.splitPanes) renderSplit(state.splitPanes);
|
|
450
|
+
if (state.projectSplitViewActiveId !== prev.projectSplitViewActiveId || state.projectSplitViews !== prev.projectSplitViews ||
|
|
451
|
+
state.projectSplitViewPreviewTree !== prev.projectSplitViewPreviewTree) renderSplit(state.splitPanes);
|
|
414
452
|
if (state.permissionModePendingByTarget !== prev.permissionModePendingByTarget || state.connected !== prev.connected ||
|
|
415
453
|
state.skipPermsEnabled !== prev.skipPermsEnabled) syncPaneTitles();
|
|
416
454
|
// Switching to a session outside the open split (new_session, palette,
|
|
@@ -442,7 +480,7 @@ export function initSplitView() {
|
|
|
442
480
|
return;
|
|
443
481
|
}
|
|
444
482
|
var projectedIds = splitGroupMemberIds(currentGroup);
|
|
445
|
-
if (projectedIds.length < 2
|
|
483
|
+
if (projectedIds.length < 2) return;
|
|
446
484
|
var projectedPanes = projectedIds.map(function (id) { return paneForSession(id); });
|
|
447
485
|
var projectedKey = projectedPanes.map(function (pane) { return pane.slug + "#" + pane.sessionId; }).join("|");
|
|
448
486
|
var currentKey = split.panes.map(function (pane) { return pane.slug + "#" + pane.sessionId; }).join("|");
|
|
@@ -456,5 +494,6 @@ export function initSplitView() {
|
|
|
456
494
|
if (confirmed) store.set({ splitPanes: { groupId: confirmed.id, panes: split.panes } });
|
|
457
495
|
}
|
|
458
496
|
});
|
|
497
|
+
initSplitViewPresentations(host);
|
|
459
498
|
renderSplit(store.get('splitPanes'));
|
|
460
499
|
}
|
|
@@ -24,6 +24,12 @@ function openCount() {
|
|
|
24
24
|
return count;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function syncToggleAvailability() {
|
|
28
|
+
var buttons = document.querySelectorAll("#sticky-notes-toggle-btn");
|
|
29
|
+
var hidden = openCount() === 0;
|
|
30
|
+
for (var i = 0; i < buttons.length; i++) buttons[i].hidden = hidden;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
// The browser pane registers here rather than being imported, so the canvas
|
|
28
34
|
// module and the browser module never form an import cycle.
|
|
29
35
|
var browserRefresh = null;
|
|
@@ -103,8 +109,6 @@ function syncFlightControls() {
|
|
|
103
109
|
buttons[i].setAttribute("aria-label", label);
|
|
104
110
|
buttons[i].setAttribute("aria-pressed", hidden ? "true" : "false");
|
|
105
111
|
buttons[i].title = label;
|
|
106
|
-
var icon = buttons[i].querySelector("[data-lucide]");
|
|
107
|
-
if (icon) icon.setAttribute("data-lucide", hidden ? "eye" : "eye-off");
|
|
108
112
|
}
|
|
109
113
|
refreshIcons();
|
|
110
114
|
}
|
|
@@ -158,6 +162,8 @@ function reclampAllNotes() {
|
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
export function initStickyNotes() {
|
|
165
|
+
syncToggleAvailability();
|
|
166
|
+
|
|
161
167
|
// Close format toolbar on outside click
|
|
162
168
|
document.addEventListener("mousedown", function (e) {
|
|
163
169
|
if (isFormatToolbarOpen() && !e.target.closest(".sn-format-toolbar") && !e.target.closest(".sticky-note-text") && !e.target.closest(".sticky-note-rendered")) {
|
|
@@ -239,6 +245,7 @@ export function createNote() {
|
|
|
239
245
|
}
|
|
240
246
|
|
|
241
247
|
function updateBadge() {
|
|
248
|
+
syncToggleAvailability();
|
|
242
249
|
var badge = document.querySelector(".sticky-notes-count");
|
|
243
250
|
if (!badge) return;
|
|
244
251
|
var count = openCount();
|