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.
Files changed (146) hide show
  1. package/lib/clay-control-presentation-binding.js +5 -3
  2. package/lib/clay-product-control-tools.js +3 -3
  3. package/lib/clay-product-control.js +4 -2
  4. package/lib/clay-screen-controls.js +18 -3
  5. package/lib/clay-split-view-awareness.js +35 -0
  6. package/lib/clay-split-view-controls.js +122 -0
  7. package/lib/clay-split-view-input.js +20 -0
  8. package/lib/clay-split-view-render-proof.js +22 -0
  9. package/lib/pane-asset-manifest.js +63 -0
  10. package/lib/project-connection.js +19 -10
  11. package/lib/project-session-actions.js +1 -0
  12. package/lib/project-session-delete.js +2 -2
  13. package/lib/project-session-folders.js +1 -0
  14. package/lib/project-sessions.js +6 -1
  15. package/lib/project-split-view-document.js +33 -0
  16. package/lib/project-split-view-names.js +67 -0
  17. package/lib/project-split-view-organization.js +23 -0
  18. package/lib/project-split-views.js +479 -0
  19. package/lib/project-worker-conversation.js +158 -0
  20. package/lib/project-worker-creation-proposal.js +3 -4
  21. package/lib/project-worker-proposal.js +1 -1
  22. package/lib/project-worker-removal.js +101 -0
  23. package/lib/project.js +28 -1
  24. package/lib/public/app.js +32 -13
  25. package/lib/public/css/base.css +9 -0
  26. package/lib/public/css/filebrowser.css +0 -1
  27. package/lib/public/css/pane-header-controls.css +100 -0
  28. package/lib/public/css/pane.css +24 -5
  29. package/lib/public/css/popup-chat.css +123 -0
  30. package/lib/public/css/right-workbench.css +50 -18
  31. package/lib/public/css/split-view-layout.css +460 -0
  32. package/lib/public/css/split-view-worker-activity.css +97 -0
  33. package/lib/public/css/sticky-notes.css +13 -0
  34. package/lib/public/index.html +3 -3
  35. package/lib/public/modules/app-connection.js +4 -1
  36. package/lib/public/modules/app-messages.js +25 -8
  37. package/lib/public/modules/chat-render-runtime.js +45 -0
  38. package/lib/public/modules/clay-control-copy.js +3 -0
  39. package/lib/public/modules/clay-navigation-render.js +1 -1
  40. package/lib/public/modules/clay-owner-navigation.js +15 -5
  41. package/lib/public/modules/clay-split-view-screen.js +74 -0
  42. package/lib/public/modules/clay-tool-activity.js +5 -0
  43. package/lib/public/modules/command-palette.js +30 -28
  44. package/lib/public/modules/contextual-clay-session.js +26 -2
  45. package/lib/public/modules/contextual-clay.js +5 -2
  46. package/lib/public/modules/contextual-mate-sessions.js +2 -0
  47. package/lib/public/modules/generation-stop.js +7 -1
  48. package/lib/public/modules/knowledge-workbench-entry.js +43 -0
  49. package/lib/public/modules/markdown.js +11 -0
  50. package/lib/public/modules/mate-knowledge-workbench.js +2 -7
  51. package/lib/public/modules/mate-sidebar.js +1 -1
  52. package/lib/public/modules/palette-clay-conversation.js +87 -0
  53. package/lib/public/modules/palette-render-helpers.js +25 -0
  54. package/lib/public/modules/pane-activation.js +46 -7
  55. package/lib/public/modules/pane-binding.js +12 -2
  56. package/lib/public/modules/pane-bridge.js +12 -3
  57. package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
  58. package/lib/public/modules/pane-feature-assets.js +35 -0
  59. package/lib/public/modules/pane-search-receipt.js +12 -0
  60. package/lib/public/modules/permission-control.js +6 -6
  61. package/lib/public/modules/popup-chat-rendering.js +244 -0
  62. package/lib/public/modules/project-activation.js +2 -2
  63. package/lib/public/modules/qrcode.js +12 -1
  64. package/lib/public/modules/right-workbench.js +68 -0
  65. package/lib/public/modules/search-hints.js +79 -0
  66. package/lib/public/modules/session-context-menu.js +35 -2
  67. package/lib/public/modules/session-folder-context.js +24 -13
  68. package/lib/public/modules/session-folder-delete.js +18 -1
  69. package/lib/public/modules/session-folder-layout.js +7 -6
  70. package/lib/public/modules/session-search-pane-state.js +30 -0
  71. package/lib/public/modules/session-search.js +53 -13
  72. package/lib/public/modules/sidebar-mobile.js +3 -3
  73. package/lib/public/modules/sidebar-projects.js +1 -0
  74. package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
  75. package/lib/public/modules/sidebar-sessions.js +57 -24
  76. package/lib/public/modules/sidebar.js +20 -3
  77. package/lib/public/modules/split-group-helpers.js +1 -1
  78. package/lib/public/modules/split-group-restore.js +8 -11
  79. package/lib/public/modules/split-pair-ui.js +18 -0
  80. package/lib/public/modules/split-pane-header-state.js +45 -0
  81. package/lib/public/modules/split-pane-reconciler.js +23 -4
  82. package/lib/public/modules/split-pane-renderer.js +330 -23
  83. package/lib/public/modules/split-pane-retention.js +157 -0
  84. package/lib/public/modules/split-session-boundary.js +21 -0
  85. package/lib/public/modules/split-view-create-action.js +15 -0
  86. package/lib/public/modules/split-view-create-menu.js +83 -0
  87. package/lib/public/modules/split-view-create-request.js +23 -0
  88. package/lib/public/modules/split-view-layout-client.js +198 -0
  89. package/lib/public/modules/split-view-layout-icons.js +37 -0
  90. package/lib/public/modules/split-view-layout-scope.js +38 -0
  91. package/lib/public/modules/split-view-organization.js +73 -0
  92. package/lib/public/modules/split-view-presentation-close.js +35 -0
  93. package/lib/public/modules/split-view-presentation-drag.js +254 -0
  94. package/lib/public/modules/split-view-presentation-drop.js +57 -0
  95. package/lib/public/modules/split-view-presentation-lookup.js +44 -0
  96. package/lib/public/modules/split-view-presentation-renderer.js +494 -0
  97. package/lib/public/modules/split-view-presentations.js +495 -0
  98. package/lib/public/modules/split-view-shell-controls.js +28 -0
  99. package/lib/public/modules/split-view-sidebar-menu.js +42 -0
  100. package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
  101. package/lib/public/modules/split-view-singleton.js +21 -0
  102. package/lib/public/modules/split-view-worker-activity.js +94 -0
  103. package/lib/public/modules/split-view-worker-controls.js +30 -0
  104. package/lib/public/modules/split-view-worker-removal.js +137 -0
  105. package/lib/public/modules/split-view-worker-scroll.js +57 -0
  106. package/lib/public/modules/split-view-worker-transcript.js +46 -0
  107. package/lib/public/modules/split-view-workers.js +352 -0
  108. package/lib/public/modules/split-view.js +61 -22
  109. package/lib/public/modules/sticky-notes.js +9 -2
  110. package/lib/public/modules/tools.js +25 -5
  111. package/lib/public/modules/workbench-layout-preference.js +76 -0
  112. package/lib/public/pane-assets/app-OJEQXBBS.js +1 -0
  113. package/lib/public/pane-assets/chunk-4SMIVAB4.js +1 -0
  114. package/lib/public/pane-assets/chunk-623V5CEO.js +1 -0
  115. package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
  116. package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
  117. package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
  118. package/lib/public/pane-assets/chunk-GML2R5JE.js +167 -0
  119. package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
  120. package/lib/public/pane-assets/chunk-I5P3MWQG.js +1 -0
  121. package/lib/public/pane-assets/chunk-UVGOPFEX.js +2 -0
  122. package/lib/public/pane-assets/manifest.json +551 -0
  123. package/lib/public/pane-assets/style-GN7R6XKV.css +1 -0
  124. package/lib/public/style.css +5 -1
  125. package/lib/server-owner-ui.js +6 -2
  126. package/lib/server-owner-view-observation.js +34 -0
  127. package/lib/server-palette.js +1 -0
  128. package/lib/server-pane-bootstrap.js +24 -0
  129. package/lib/server-settings.js +2 -0
  130. package/lib/server-static-assets.js +52 -0
  131. package/lib/server-workbench-layout-settings.js +56 -0
  132. package/lib/server.js +6 -45
  133. package/lib/session-activity-state.js +28 -0
  134. package/lib/session-list-delivery.js +17 -0
  135. package/lib/session-pair-turn-control.js +1 -6
  136. package/lib/session-search.js +3 -0
  137. package/lib/session-split-group-anchors.js +2 -2
  138. package/lib/session-split-group-roles.js +5 -8
  139. package/lib/session-split-group-v2-store.js +0 -3
  140. package/lib/sessions.js +16 -6
  141. package/lib/split-view-layout.js +132 -0
  142. package/lib/users-workbench-layout-preferences.js +33 -0
  143. package/lib/users.js +3 -0
  144. package/lib/workspace-query-service.js +1 -5
  145. package/lib/ws-schema.js +15 -0
  146. 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 || panes.length > 3) {
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 || group.members.length > 3) return false;
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(openGroup);
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('splitPanes')) return;
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 openDriverGroup(driver) {
341
- var state = store.snap();
342
- if (!driver || driver.sessionRole !== "driver" || !isCurrentProjectSessionReady(state, getWs())) return false;
343
- var groups = state.splitGroups || [];
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
- var roles = splitGroupRoles(group);
347
- if (!roles || roles.driverId !== driver.id || typeof group.id !== "string" || !group.id) continue;
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 && worker.parentSessionId === driver.id;
380
+ return worker && worker.sessionRole === "worker" && worker.parentAvailable === true &&
381
+ worker.parentSessionId === driver.id && (worker.ownerId || null) === (driver.ownerId || null);
351
382
  });
352
- // Keep mounted conversations when selecting their Driver again.
353
- if (available) return openGroup(group);
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 (!store.get('splitPanes') || event.button !== 0) return;
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 (openDriverGroup(session)) return;
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 || projectedIds.length > 3) return;
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();