clay-server 5.2.1-beta.2 → 5.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +222 -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 -41
- package/lib/public/css/popup-chat.css +123 -0
- package/lib/public/css/right-workbench.css +50 -18
- package/lib/public/css/session-folders.css +1 -1
- package/lib/public/css/split-view-layout.css +463 -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-favicon.js +1 -1
- package/lib/public/modules/app-messages.js +31 -9
- package/lib/public/modules/chat-render-runtime.js +54 -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/filebrowser.js +6 -2
- package/lib/public/modules/generation-stop.js +7 -1
- package/lib/public/modules/knowledge-workbench-entry.js +43 -0
- package/lib/public/modules/markdown-view-mode.js +34 -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-file-bridge.js +8 -3
- 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-collapse.js +2 -1
- 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-dnd.js +4 -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 +278 -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 +498 -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 +418 -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/modules/worker-created-presentation.js +37 -0
- package/lib/public/pane-assets/app-2DRBYY3L.js +1 -0
- package/lib/public/pane-assets/chunk-4BM7UVNG.js +2 -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-C4RVVYNT.js +1 -0
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
- package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
- package/lib/public/pane-assets/chunk-O6MQL6MR.js +167 -0
- package/lib/public/pane-assets/chunk-PO2JOHHW.js +1 -0
- package/lib/public/pane-assets/chunk-UMGHINOT.js +1 -0
- package/lib/public/pane-assets/manifest.json +553 -0
- package/lib/public/pane-assets/style-63EZCREE.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 +16 -0
- package/package.json +6 -3
|
@@ -0,0 +1,418 @@
|
|
|
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
|
+
var socket = getWs();
|
|
79
|
+
if (state.subscriptionId && socket && socket === state.socket && socket.readyState === 1) {
|
|
80
|
+
try { socket.send(JSON.stringify({ type: 'split_worker_conversation_request', watch: false,
|
|
81
|
+
requestId: state.subscriptionId, parentSessionOriginId: state.driverOriginId,
|
|
82
|
+
workerSessionOriginId: state.workerOriginId, generation: state.generation })); } catch (_error) {}
|
|
83
|
+
}
|
|
84
|
+
if (state.fillTimer) clearTimeout(state.fillTimer);
|
|
85
|
+
if (state.requestTimer) clearTimeout(state.requestTimer);
|
|
86
|
+
store.set({ splitViewWorkerConversation: null });
|
|
87
|
+
var element = popover(); if (element) element.remove();
|
|
88
|
+
if (returnFocus) {
|
|
89
|
+
var chip = document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"] [data-worker-origin="' + CSS.escape(state.workerOriginId) + '"]');
|
|
90
|
+
if (chip && chip.isConnected && !chip.closest('.split-pane').hidden) chip.focus();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function position(element, anchor) {
|
|
94
|
+
if (!element || !anchor || !anchor.isConnected) return;
|
|
95
|
+
var rect = anchor.getBoundingClientRect(), width = Math.min(430, window.innerWidth - 20), height = Math.min(440, window.innerHeight - 24);
|
|
96
|
+
element.style.width = width + "px"; element.style.height = height + "px";
|
|
97
|
+
element.style.left = Math.max(10, Math.min(rect.left, window.innerWidth - width - 10)) + "px";
|
|
98
|
+
element.style.top = Math.max(10, Math.min(rect.bottom + 6, window.innerHeight - height - 10)) + "px";
|
|
99
|
+
}
|
|
100
|
+
function requestPage(state, before, append, refresh) {
|
|
101
|
+
var current = store.get('splitViewWorkerConversation');
|
|
102
|
+
if (!state || !current || state.requestId !== current.requestId || current.inFlight || workerCursorKey(before) === null || !ensurePopover()) return;
|
|
103
|
+
state = store.get('splitViewWorkerConversation');
|
|
104
|
+
if (append && workerCursorKey(before) !== workerCursorKey(state.nextBefore)) return;
|
|
105
|
+
if (state.fillTimer) clearTimeout(state.fillTimer);
|
|
106
|
+
var ws = getWs();
|
|
107
|
+
if (!store.get('connected') || !ws || ws.readyState !== 1) {
|
|
108
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, state, { status: "error", error: "Worker conversation is unavailable while disconnected.", append: false }) });
|
|
109
|
+
renderContent(); return;
|
|
110
|
+
}
|
|
111
|
+
var requestId = "worker-read-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 9);
|
|
112
|
+
var subscribe = !state.subscriptionId || !state.subscriptionConfirmed;
|
|
113
|
+
var pending = Object.assign({}, state, { subscriptionId: subscribe ? requestId : state.subscriptionId, requestId: requestId, socket: ws, projectSlug: store.get('currentSlug'), ownerId: store.get('myUserId') || null,
|
|
114
|
+
socketGeneration: store.get('projectSplitViewSocketGeneration') || 0, status: "loading", error: null, inFlight: true, fillTimer: null, requestedBefore: before == null ? null : before, append: append === true, refreshing: refresh === true, refreshPending: false });
|
|
115
|
+
pending.requestTimer = setTimeout(function () {
|
|
116
|
+
var current = store.get('splitViewWorkerConversation');
|
|
117
|
+
if (!current || current.requestId !== requestId || !current.inFlight) return;
|
|
118
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, current, { status: 'error', inFlight: false, append: false,
|
|
119
|
+
requestTimer: null, error: 'Worker conversation request timed out. Scroll up or retry to load it again.' }) });
|
|
120
|
+
renderContent();
|
|
121
|
+
}, 15000);
|
|
122
|
+
store.set({ splitViewWorkerConversation: pending }); renderContent();
|
|
123
|
+
var last = refresh && state.messages[state.messages.length - 1];
|
|
124
|
+
var after = last && Number.isInteger(last.recordIndex) && Number.isInteger(last.end) ? { index: last.recordIndex, offset: last.end } : undefined;
|
|
125
|
+
try {
|
|
126
|
+
ws.send(JSON.stringify({ type: "split_worker_conversation_request", requestId: requestId,
|
|
127
|
+
parentSessionOriginId: state.driverOriginId, workerSessionOriginId: state.workerOriginId,
|
|
128
|
+
generation: state.generation, before: before, after: after, watch: subscribe ? true : undefined }));
|
|
129
|
+
} catch (_error) {
|
|
130
|
+
var current = store.get('splitViewWorkerConversation');
|
|
131
|
+
if (current && current.requestId === requestId) {
|
|
132
|
+
clearTimeout(current.requestTimer);
|
|
133
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, current, { status: "error", error: "Worker conversation request could not be sent.", inFlight: false, append: false }) });
|
|
134
|
+
renderContent();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function open(chip, driver, worker) {
|
|
139
|
+
var existing = store.get('splitViewWorkerConversation');
|
|
140
|
+
if (existing && existing.driverOriginId === driver.sessionOriginId && existing.workerOriginId === worker.sessionOriginId && existing.generation === worker.workerGeneration) { close(true); return; }
|
|
141
|
+
if (existing) close(false);
|
|
142
|
+
var state = { requestId: null, driverOriginId: driver.sessionOriginId, workerOriginId: worker.sessionOriginId,
|
|
143
|
+
generation: worker.workerGeneration, driverTitle: driver.title || "Driver session", workerTitle: worker.title || "Worker",
|
|
144
|
+
sourceBinding: chip.closest('.split-pane').querySelector('.split-pane-frame').clayPane.identity().binding,
|
|
145
|
+
status: "loading", messages: [], hasMore: false, nextBefore: null, append: false, inFlight: false, initialPage: true };
|
|
146
|
+
store.set({ splitViewWorkerConversation: state }); ensurePopover(); renderContent();
|
|
147
|
+
var element = popover(); position(element, chip);
|
|
148
|
+
requestPage(state, undefined, false);
|
|
149
|
+
var closeButton = element && element.querySelector('[aria-label="Close Worker conversation"]'); if (closeButton) closeButton.focus();
|
|
150
|
+
}
|
|
151
|
+
function ensurePopover() {
|
|
152
|
+
var state = store.get('splitViewWorkerConversation');
|
|
153
|
+
var element = popover();
|
|
154
|
+
if (!state) { if (element) element.remove(); return; }
|
|
155
|
+
var driver = exactDriver(state.driverOriginId), worker = exactWorker(driver, state.workerOriginId, state.generation);
|
|
156
|
+
var sourcePane = document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"]');
|
|
157
|
+
var source = presentationSource(sourcePane, driver, state.status === 'error' && state.sourceBinding);
|
|
158
|
+
if (!driver || !worker || !source || sourcePane.hidden || !state.sourceBinding || !samePaneBinding(source.binding || source.live && source.live.binding, state.sourceBinding) ||
|
|
159
|
+
source.view && store.get('projectSplitViewFocusedOrigin') && store.get('projectSplitViewFocusedOrigin') !== state.driverOriginId) {
|
|
160
|
+
close(false); return;
|
|
161
|
+
}
|
|
162
|
+
if (!element) {
|
|
163
|
+
element = document.createElement("section"); element.id = "split-view-worker-popover"; element.className = "split-view-worker-popover";
|
|
164
|
+
element.setAttribute("role", "dialog"); element.setAttribute("aria-label", "Read-only Worker conversation"); document.body.appendChild(element);
|
|
165
|
+
var header = document.createElement("header"); header.className = "split-view-worker-popover-header";
|
|
166
|
+
var title = document.createElement("strong"); title.className = "worker-popover-title"; header.appendChild(title);
|
|
167
|
+
var closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.setAttribute("aria-label", "Close Worker conversation"); closeButton.innerHTML = iconHtml("x");
|
|
168
|
+
closeButton.addEventListener("click", function () { close(true); }); header.appendChild(closeButton); element.appendChild(header);
|
|
169
|
+
var source = document.createElement("div"); source.className = "split-view-worker-popover-source"; element.appendChild(source);
|
|
170
|
+
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);
|
|
171
|
+
body.addEventListener('scroll', function () { loadEarlier(body, true); }, { passive: true });
|
|
172
|
+
body.addEventListener('wheel', function (event) { if (event.deltaY < 0) loadEarlier(body, true); }, { passive: true });
|
|
173
|
+
var footer = document.createElement("footer"); footer.className = "split-view-worker-popover-footer"; element.appendChild(footer);
|
|
174
|
+
}
|
|
175
|
+
return element;
|
|
176
|
+
}
|
|
177
|
+
function renderContent() {
|
|
178
|
+
var state = store.get('splitViewWorkerConversation'), element = ensurePopover();
|
|
179
|
+
if (!state || !element) return;
|
|
180
|
+
var title = element.querySelector('.worker-popover-title'), source = element.querySelector('.split-view-worker-popover-source');
|
|
181
|
+
var body = element.querySelector('.split-view-worker-popover-body'), footer = element.querySelector('.split-view-worker-popover-footer');
|
|
182
|
+
if (title) title.textContent = state.workerTitle;
|
|
183
|
+
if (source) source.textContent = state.driverTitle + " · " + state.workerTitle + " · Gen " + state.generation;
|
|
184
|
+
var footerFocused = footer && footer.contains(document.activeElement);
|
|
185
|
+
if (body && (state.renderedMessages !== state.messages || !state.messages.length)) {
|
|
186
|
+
var anchor = state.prepend ? captureWorkerAnchor(body) : null;
|
|
187
|
+
var disclosures = captureWorkerDisclosures(body);
|
|
188
|
+
var followTail = body.scrollHeight - body.clientHeight - body.scrollTop < 48;
|
|
189
|
+
var oldTop = body.scrollTop, wasFocused = document.activeElement === body;
|
|
190
|
+
body.textContent = "";
|
|
191
|
+
if (state.status === "loading" && !state.messages.length) body.textContent = "Loading conversation…";
|
|
192
|
+
else if (state.status === "error" && !state.messages.length) body.textContent = state.error || "Conversation is unavailable.";
|
|
193
|
+
else if (!state.messages.length) body.textContent = "No recorded conversation.";
|
|
194
|
+
appendWorkerTranscript(body, state.messages);
|
|
195
|
+
restoreWorkerDisclosures(body, disclosures);
|
|
196
|
+
if ((state.initialPage || state.liveRefresh && followTail) && state.status === 'ready') body.scrollTop = body.scrollHeight;
|
|
197
|
+
else if (anchor) restoreWorkerAnchor(body, anchor);
|
|
198
|
+
else body.scrollTop = oldTop;
|
|
199
|
+
if (wasFocused) body.focus({ preventScroll: true });
|
|
200
|
+
}
|
|
201
|
+
if (body) store.set({ splitViewWorkerConversation: Object.assign({}, state, { renderedMessages: state.messages, prepend: false, liveRefresh: false,
|
|
202
|
+
initialPage: state.status === 'ready' ? false : state.initialPage }) });
|
|
203
|
+
if (footer) {
|
|
204
|
+
footer.textContent = state.status === 'error' ? 'Read-only conversation' : state.refreshing ? 'Updating live conversation…' : state.subscriptionConfirmed ? 'Read-only · Live updates' : 'Read-only conversation';
|
|
205
|
+
if (state.status === "error") { var status = document.createElement('span'); status.setAttribute('role', 'status'); status.textContent = state.error || "Conversation is unavailable."; footer.appendChild(status); }
|
|
206
|
+
var status = document.createElement('span'); status.className = 'split-view-worker-page-status'; status.setAttribute('role', 'status');
|
|
207
|
+
status.textContent = state.inFlight ? (state.refreshing ? 'Receiving Worker activity…' : state.messages.length ? 'Loading earlier messages…' : 'Loading conversation…') : state.hasMore ? 'Scroll up for earlier messages' : state.messages.length ? 'Beginning of conversation' : '';
|
|
208
|
+
footer.appendChild(status);
|
|
209
|
+
if (state.status === 'error') {
|
|
210
|
+
var retry = document.createElement('button'); retry.type = 'button'; retry.textContent = 'Retry loading conversation';
|
|
211
|
+
retry.addEventListener('click', function () { var current = store.get('splitViewWorkerConversation'); if (current) requestPage(current, current.requestedBefore, current.messages.length > 0); });
|
|
212
|
+
footer.appendChild(retry);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (footerFocused) { var nextFocus = footer.querySelector('button') || body; if (nextFocus) nextFocus.focus({ preventScroll: true }); }
|
|
216
|
+
refreshIcons();
|
|
217
|
+
scheduleWorkerFill(body);
|
|
218
|
+
}
|
|
219
|
+
function loadEarlier(body, intentional) {
|
|
220
|
+
var state = store.get('splitViewWorkerConversation');
|
|
221
|
+
if (!shouldLoadWorkerPage(state, body, intentional)) return;
|
|
222
|
+
requestPage(state, state.nextBefore, true);
|
|
223
|
+
}
|
|
224
|
+
function scheduleWorkerFill(body) {
|
|
225
|
+
var state = store.get('splitViewWorkerConversation');
|
|
226
|
+
if (!shouldLoadWorkerPage(state, body, false) || state.fillTimer) return;
|
|
227
|
+
var requestId = state.requestId;
|
|
228
|
+
var timer = setTimeout(function () {
|
|
229
|
+
var current = store.get('splitViewWorkerConversation');
|
|
230
|
+
if (!current || current.requestId !== requestId || current.fillTimer !== timer || !body.isConnected) return;
|
|
231
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, current, { fillTimer: null }) });
|
|
232
|
+
loadEarlier(body, false);
|
|
233
|
+
}, 120);
|
|
234
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, state, { fillTimer: timer }) });
|
|
235
|
+
}
|
|
236
|
+
function anchorChip(state) {
|
|
237
|
+
if (!state) return null;
|
|
238
|
+
return document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"] [data-worker-origin="' + CSS.escape(state.workerOriginId) + '"]');
|
|
239
|
+
}
|
|
240
|
+
function reanchorPopover() {
|
|
241
|
+
var state = store.get('splitViewWorkerConversation');
|
|
242
|
+
if (state) position(popover(), anchorChip(state));
|
|
243
|
+
}
|
|
244
|
+
export function handleSplitWorkerConversation(message) {
|
|
245
|
+
var state = store.get('splitViewWorkerConversation');
|
|
246
|
+
if (!state || !store.get('connected') || !message || message.type !== "split_worker_conversation" || state.socket !== getWs() || state.projectSlug !== store.get('currentSlug') ||
|
|
247
|
+
state.ownerId !== (store.get('myUserId') || null) || state.socketGeneration !== (store.get('projectSplitViewSocketGeneration') || 0) || message.requestId !== state.requestId ||
|
|
248
|
+
message.parentSessionOriginId !== state.driverOriginId || message.workerSessionOriginId !== state.workerOriginId || message.generation !== state.generation) return false;
|
|
249
|
+
var driver = exactDriver(state.driverOriginId), sourcePane = document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"]');
|
|
250
|
+
var source = presentationSource(sourcePane, driver);
|
|
251
|
+
if (!exactWorker(driver, state.workerOriginId, state.generation) || !source || !state.sourceBinding || !samePaneBinding(source.live.binding, state.sourceBinding)) { close(false); return true; }
|
|
252
|
+
if (!state.inFlight || state.status !== 'loading' || !Object.prototype.hasOwnProperty.call(message, 'pageBefore') || workerCursorKey(message.pageBefore) !== workerCursorKey(state.requestedBefore)) return false;
|
|
253
|
+
clearTimeout(state.requestTimer);
|
|
254
|
+
var error = message.error || null;
|
|
255
|
+
if (!error && (!Array.isArray(message.messages) || message.hasMore && !workerCursorAdvances(state.requestedBefore, message.nextBefore))) error = 'Worker history page did not advance. Retry loading conversation.';
|
|
256
|
+
var incoming = error ? [] : message.messages;
|
|
257
|
+
var messages = error ? state.messages : state.append ? incoming.concat(state.messages || []) : incoming.slice();
|
|
258
|
+
if (!error && state.refreshing && incoming.length) {
|
|
259
|
+
var first = incoming[0];
|
|
260
|
+
messages = state.messages.filter(function (item) {
|
|
261
|
+
return item.recordIndex < first.recordIndex || item.recordIndex === first.recordIndex && item.end <= first.start;
|
|
262
|
+
}).concat(incoming);
|
|
263
|
+
}
|
|
264
|
+
if (!error && state.refreshing && !incoming.length) messages = state.messages;
|
|
265
|
+
var refreshAgain = state.refreshPending || message.moreRecent === true;
|
|
266
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, state, { status: error ? 'error' : 'ready', error: error,
|
|
267
|
+
messages: messages, subscriptionConfirmed: !error || state.subscriptionConfirmed, inFlight: false, requestTimer: null, nextBefore: error || state.refreshing && state.messages.length ? state.nextBefore : message.nextBefore,
|
|
268
|
+
hasMore: error || state.refreshing && state.messages.length ? state.hasMore : message.hasMore === true,
|
|
269
|
+
liveRefresh: !error && state.refreshing, refreshing: false, refreshPending: false, prepend: !error && state.append, append: false }) });
|
|
270
|
+
renderContent();
|
|
271
|
+
if (refreshAgain && !error) refreshWorkerConversation();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
function refreshWorkerConversation() {
|
|
275
|
+
var state = store.get('splitViewWorkerConversation');
|
|
276
|
+
if (!state || !store.get('connected') || !ensurePopover()) return;
|
|
277
|
+
if (state.inFlight) {
|
|
278
|
+
if (!state.refreshPending) store.set({ splitViewWorkerConversation: Object.assign({}, state, { refreshPending: true }) });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
requestPage(state, undefined, false, true);
|
|
282
|
+
}
|
|
283
|
+
export function handleWorkerConversationChanged(message) {
|
|
284
|
+
var state = store.get('splitViewWorkerConversation');
|
|
285
|
+
if (!state || !store.get('connected') || state.socket !== getWs() || state.projectSlug !== store.get('currentSlug') ||
|
|
286
|
+
state.ownerId !== (store.get('myUserId') || null) || state.socketGeneration !== (store.get('projectSplitViewSocketGeneration') || 0) ||
|
|
287
|
+
message.subscriptionId !== state.subscriptionId || message.parentSessionOriginId !== state.driverOriginId ||
|
|
288
|
+
message.workerSessionOriginId !== state.workerOriginId || message.generation !== state.generation || !ensurePopover()) return false;
|
|
289
|
+
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
290
|
+
if (chip.dataset.workerOrigin === state.workerOriginId) {
|
|
291
|
+
var dot = chip.querySelector('.split-view-worker-dot');
|
|
292
|
+
if (dot) dot.dataset.state = message.isProcessing ? 'working' : 'ready';
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
refreshWorkerConversation();
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
export function handleWorkerSessionActivity(message) {
|
|
299
|
+
var worker = sessions().find(function (item) { return item.id === message.id && item.sessionRole === 'worker'; });
|
|
300
|
+
if (!worker || !store.get('connected')) return;
|
|
301
|
+
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
302
|
+
if (chip.dataset.workerOrigin !== worker.sessionOriginId) return;
|
|
303
|
+
var dot = chip.querySelector('.split-view-worker-dot');
|
|
304
|
+
if (dot) { dot.dataset.state = 'working'; dot.setAttribute('aria-label', 'Worker activity received'); }
|
|
305
|
+
});
|
|
306
|
+
var state = store.get('splitViewWorkerConversation');
|
|
307
|
+
if (state && state.workerOriginId === worker.sessionOriginId && state.generation === worker.workerGeneration) refreshWorkerConversation();
|
|
308
|
+
}
|
|
309
|
+
export function renderPaneWorkers(container, driver) {
|
|
310
|
+
if (!container || !driver) return;
|
|
311
|
+
reconcileWorkerRemoval(container);
|
|
312
|
+
var root = container.querySelector('.split-view-workers');
|
|
313
|
+
var data = workersFor(driver);
|
|
314
|
+
if (!root) { root = document.createElement("div"); root.className = "split-view-workers"; root.setAttribute("aria-label", "Worker conversations");
|
|
315
|
+
var frame = container.querySelector('.split-pane-frame'); if (frame) container.insertBefore(root, frame); else container.appendChild(root); }
|
|
316
|
+
var source = presentationSource(container, driver);
|
|
317
|
+
root.hidden = !source || data.items.length === 0;
|
|
318
|
+
if (!source) { if (store.get('splitViewWorkerConversation') && store.get('splitViewWorkerConversation').driverOriginId === driver.sessionOriginId) ensurePopover(); return; }
|
|
319
|
+
root.textContent = "";
|
|
320
|
+
data.items.forEach(function (worker) {
|
|
321
|
+
var pill = document.createElement("span"); pill.className = "split-view-worker-pill";
|
|
322
|
+
var button = document.createElement("button"); button.type = "button"; button.className = "split-view-worker-chip";
|
|
323
|
+
button.dataset.workerOrigin = worker.sessionOriginId; button.dataset.workerGeneration = String(worker.workerGeneration);
|
|
324
|
+
var current = data.currentIds.has(worker.id);
|
|
325
|
+
var modelLabel = worker.model || "Default model";
|
|
326
|
+
button.title = (VENDOR_NAMES[worker.vendor] || worker.vendor || "Unknown vendor") + " · " + modelLabel + " · Split Worker #" + worker.id +
|
|
327
|
+
(current ? "" : " · Archived · Gen " + worker.workerGeneration) + " · " + (worker.title || "Worker");
|
|
328
|
+
button.setAttribute("aria-label", button.title + " conversation");
|
|
329
|
+
var dot = document.createElement("span"); dot.className = "split-view-worker-dot"; dot.dataset.state = !store.get('connected') ? 'disconnected' : worker.isProcessing ? 'working' : 'ready';
|
|
330
|
+
dot.setAttribute('aria-label', !store.get('connected') ? 'Disconnected' : worker.isProcessing ? 'Worker is working' : 'Worker is idle');
|
|
331
|
+
if (VENDOR_AVATARS[worker.vendor]) {
|
|
332
|
+
var vendorIcon = document.createElement("img"); vendorIcon.className = "split-view-worker-vendor";
|
|
333
|
+
vendorIcon.src = VENDOR_AVATARS[worker.vendor]; vendorIcon.alt = ""; vendorIcon.setAttribute("aria-hidden", "true"); button.appendChild(vendorIcon);
|
|
334
|
+
}
|
|
335
|
+
var name = document.createElement("span"); name.className = "split-view-worker-name"; name.textContent = modelLabel; button.appendChild(name);
|
|
336
|
+
var identity = document.createElement("span"); identity.className = "split-view-worker-identity";
|
|
337
|
+
identity.textContent = "Split Worker #" + worker.id; button.appendChild(identity);
|
|
338
|
+
if (!current) {
|
|
339
|
+
var generation = document.createElement("span"); generation.className = "split-view-worker-generation";
|
|
340
|
+
generation.textContent = "Gen " + worker.workerGeneration; button.appendChild(generation);
|
|
341
|
+
}
|
|
342
|
+
button.dataset.workerSessionId = String(worker.id);
|
|
343
|
+
button.appendChild(dot);
|
|
344
|
+
var state = store.get('splitViewWorkerConversation');
|
|
345
|
+
button.setAttribute("aria-expanded", state && state.driverOriginId === driver.sessionOriginId && state.workerOriginId === worker.sessionOriginId ? "true" : "false");
|
|
346
|
+
button.addEventListener("click", function () { if (presentationSource(container, exactDriver(driver.sessionOriginId))) open(button, driver, worker); }); pill.appendChild(button); root.appendChild(pill);
|
|
347
|
+
appendWorkerRemoval(pill, button, container, driver, worker);
|
|
348
|
+
});
|
|
349
|
+
var activeView = (store.get('projectSplitViews') || []).find(function (view) { return view.id === store.get('projectSplitViewActiveId'); });
|
|
350
|
+
var group = configuredGroup(driver);
|
|
351
|
+
if ((activeView && activeView.sessions.some(function (session) { return session.sessionOriginId === driver.sessionOriginId; }) || source.singleton) && group) {
|
|
352
|
+
var controls = document.createElement('button'); controls.type = 'button'; controls.className = 'split-view-worker-controls';
|
|
353
|
+
controls.textContent = 'Controls'; controls.setAttribute('aria-label', 'Open Worker controls for ' + (driver.title || 'Driver session'));
|
|
354
|
+
controls.dataset.driverOrigin = driver.sessionOriginId; controls.dataset.groupId = String(group.id);
|
|
355
|
+
controls.addEventListener('click', function (event) {
|
|
356
|
+
event.preventDefault(); event.stopPropagation();
|
|
357
|
+
var frame = container.querySelector('.split-pane-frame'), live = frame && frame.clayPane && frame.clayPane.identity();
|
|
358
|
+
var currentSource = presentationSource(container, exactDriver(driver.sessionOriginId));
|
|
359
|
+
if (!frame || !live || !live.ready || !currentSource || !frame.isConnected || frame.dataset.sessionOriginId !== driver.sessionOriginId ||
|
|
360
|
+
Number(frame.dataset.sessionId) !== driver.id || live.sessionId !== driver.id || live.binding.sessionOriginId !== driver.sessionOriginId ||
|
|
361
|
+
frame.dataset.projectSlug !== store.get('currentSlug') || configuredGroup(exactDriver(driver.sessionOriginId)) !== group) return;
|
|
362
|
+
document.dispatchEvent(new CustomEvent('clay-split-view-open-worker-controls', { detail: {
|
|
363
|
+
driverOriginId: driver.sessionOriginId, driverId: driver.id, groupId: group.id, projectSlug: store.get('currentSlug'),
|
|
364
|
+
ownerId: driver.ownerId || null, workerOrigins: group.members.map(function (id) {
|
|
365
|
+
var member = sessions().find(function (session) { return session.id === id; }); return member && member.sessionRole === 'worker' ? member.sessionOriginId : null;
|
|
366
|
+
})
|
|
367
|
+
} }));
|
|
368
|
+
});
|
|
369
|
+
root.appendChild(controls);
|
|
370
|
+
}
|
|
371
|
+
var conversation = store.get('splitViewWorkerConversation');
|
|
372
|
+
if (conversation) ensurePopover();
|
|
373
|
+
}
|
|
374
|
+
document.addEventListener("keydown", function (event) { if (event.key === "Escape" && store.get('splitViewWorkerConversation')) { event.preventDefault(); event.stopImmediatePropagation(); close(true); } }, true);
|
|
375
|
+
document.addEventListener("click", function (event) {
|
|
376
|
+
if (!store.get('splitViewWorkerConversation')) return;
|
|
377
|
+
var element = popover(); if (element && element.contains(event.target)) return;
|
|
378
|
+
if (event.target.closest && event.target.closest('.split-view-worker-chip')) return;
|
|
379
|
+
var interactive = event.target.closest && event.target.closest('button, a, input, textarea, select, [contenteditable="true"], [tabindex]:not([tabindex="-1"])');
|
|
380
|
+
close(!interactive);
|
|
381
|
+
}, true);
|
|
382
|
+
window.addEventListener("resize", reanchorPopover);
|
|
383
|
+
window.addEventListener("scroll", function (event) {
|
|
384
|
+
var element = popover();
|
|
385
|
+
if (element && event.target && (event.target === element || event.target.nodeType && element.contains(event.target))) return;
|
|
386
|
+
reanchorPopover();
|
|
387
|
+
}, true);
|
|
388
|
+
store.subscribe(function (state, previous) {
|
|
389
|
+
if (state.paneBindingSessions !== previous.paneBindingSessions || state.connected !== previous.connected) {
|
|
390
|
+
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
391
|
+
var worker = sessions().find(function (item) { return item.sessionOriginId === chip.dataset.workerOrigin; });
|
|
392
|
+
var dot = chip.querySelector('.split-view-worker-dot');
|
|
393
|
+
if (dot) dot.dataset.state = !state.connected ? 'disconnected' : worker && worker.isProcessing ? 'working' : 'ready';
|
|
394
|
+
});
|
|
395
|
+
if (state.connected && state.splitViewWorkerConversation) refreshWorkerConversation();
|
|
396
|
+
}
|
|
397
|
+
if (state.splitViewWorkerConversation && (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId ||
|
|
398
|
+
state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration)) {
|
|
399
|
+
close(false);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (state.splitViewWorkerConversation && (state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.projectSplitViews !== previous.projectSplitViews ||
|
|
403
|
+
state.projectSplitViewSingletonOrigin !== previous.projectSplitViewSingletonOrigin || state.splitPanes !== previous.splitPanes ||
|
|
404
|
+
state.projectSplitViewFocusedOrigin !== previous.projectSplitViewFocusedOrigin || state.paneBindingSessions !== previous.paneBindingSessions)) {
|
|
405
|
+
if (state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.projectSplitViewFocusedOrigin !== previous.projectSplitViewFocusedOrigin ||
|
|
406
|
+
state.paneBindingSessions !== previous.paneBindingSessions) ensurePopover();
|
|
407
|
+
else renderContent();
|
|
408
|
+
}
|
|
409
|
+
if (state.connected !== previous.connected && state.connected === false) {
|
|
410
|
+
var conversation = state.splitViewWorkerConversation;
|
|
411
|
+
if (conversation) {
|
|
412
|
+
if (conversation.requestTimer) clearTimeout(conversation.requestTimer);
|
|
413
|
+
if (conversation.fillTimer) clearTimeout(conversation.fillTimer);
|
|
414
|
+
store.set({ splitViewWorkerConversation: Object.assign({}, conversation, { status: "error", error: "Worker conversation connection was lost.", inFlight: false, append: false }) });
|
|
415
|
+
renderContent();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
});
|
|
@@ -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
|
}
|