clay-server 5.2.1-beta.1 → 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 (151) 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/sdk-bridge.js +16 -3
  126. package/lib/server-owner-ui.js +6 -2
  127. package/lib/server-owner-view-observation.js +34 -0
  128. package/lib/server-palette.js +1 -0
  129. package/lib/server-pane-bootstrap.js +24 -0
  130. package/lib/server-settings.js +2 -0
  131. package/lib/server-static-assets.js +52 -0
  132. package/lib/server-workbench-layout-settings.js +56 -0
  133. package/lib/server.js +6 -45
  134. package/lib/session-activity-state.js +28 -0
  135. package/lib/session-list-delivery.js +17 -0
  136. package/lib/session-pair-turn-control.js +1 -6
  137. package/lib/session-search.js +3 -0
  138. package/lib/session-split-group-anchors.js +2 -2
  139. package/lib/session-split-group-roles.js +5 -8
  140. package/lib/session-split-group-v2-store.js +0 -3
  141. package/lib/sessions.js +16 -6
  142. package/lib/split-view-layout.js +132 -0
  143. package/lib/users-workbench-layout-preferences.js +33 -0
  144. package/lib/users.js +3 -0
  145. package/lib/workspace-query-service.js +1 -5
  146. package/lib/ws-schema.js +15 -0
  147. package/lib/yoke/adapters/codex.js +1 -0
  148. package/lib/yoke/codex-app-server.js +3 -1
  149. package/lib/yoke/codex-idle-recovery.js +73 -20
  150. package/lib/yoke/codex-recovery-admission.js +65 -4
  151. package/package.json +6 -3
@@ -0,0 +1,495 @@
1
+ import { preserveReconnectingPresentation } from './split-pane-retention.js';
2
+ import { layoutScope, currentLayoutScope, presentationTree } from './split-view-layout-scope.js';
3
+ import { validPresentationClose } from './split-view-presentation-close.js';
4
+ import { applyPresentationDrop } from './split-view-presentation-drop.js';
5
+ import { closeSessionContextMenu } from './session-context-menu.js';
6
+ import { openSplitViewMenu, closeSplitViewMenu as closeGroupMenus } from './split-view-sidebar-menu.js';
7
+ import { store } from './store.js';
8
+ import { organizeViewAction } from './split-view-organization.js';
9
+ import { getWs } from './ws-ref.js';
10
+ import { getCachedSessions } from './sidebar-sessions.js';
11
+ import { renderPresentationView, initPresentationRenderer, hidePresentationView, closePresentationArrange } from './split-view-presentation-renderer.js';
12
+ import { initPresentationDrag, restorePresentationDrag } from './split-view-presentation-drag.js';
13
+ import { detachTuiView } from './session-tui-view.js';
14
+ import { checkComposerDeparture } from './session-rich-drafts.js';
15
+ import { showToast } from './utils.js';
16
+ import { preset, listIds, validateTree, remove, arrangeRegion } from './split-view-layout-client.js';
17
+ import { viewIdForOrigin, updateActiveSession, exactDriverForOrigin, restorablePresentation } from './split-view-presentation-lookup.js';
18
+ import { groupedSessionIds } from './split-group-helpers.js';
19
+ import { handleSplitViewCreateEvent } from './split-view-create-action.js';
20
+ import { initSplitViewCreateMenu } from './split-view-create-menu.js';
21
+ import { currentSingletonPresentation } from './split-view-singleton.js';
22
+ var host = null;
23
+ var nativeApp = null;
24
+ var mainPanels = null;
25
+ var stickyContainer = null;
26
+ var stickyHome = null;
27
+ var stickyAnchor = null;function contextKey(state) {
28
+ return [state.currentSlug || "", state.currentProjectOwnerId || "", state.myUserId || "", state.projectSplitViewSocketGeneration || 0].join("|");
29
+ }
30
+ function projectReady(state) {
31
+ return getWs() !== state.splitIdentityBlockedSocket && !state.paneMode && !state.dmMode && state.currentSlug && state.activeProjectSlug === state.currentSlug &&
32
+ state.sessionListProjectSlug === state.currentSlug && state.authIdentityLoaded && state.connected;
33
+ }
34
+ function resetForContext() {
35
+ var state = store.snap();
36
+ var key = contextKey(state);
37
+ if (state.projectSplitViewContextKey === key) return false;
38
+ var uncertain = state.projectSplitViewPending ? "Connection or project changed during a Split view change. Refreshing saved state before another change." : "";
39
+ cleanupTransientInteractions(state);
40
+ store.set({ projectSplitViews: [], projectSplitViewRevision: 0, projectSplitViewContextKey: key,
41
+ projectSplitViewHydrated: false, projectSplitViewPending: null, projectSplitViewError: uncertain,
42
+ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, projectSplitViewStandaloneTarget: null,
43
+ projectSplitViewDrag: null, projectSplitViewDividerDrag: null, projectSplitViewRequestSocket: null,
44
+ projectSplitViewRequestGeneration: null, projectSplitViewRequestOwnerId: null, projectSplitViewRequestSlug: null });
45
+ return true;
46
+ }
47
+ function cleanupTransientInteractions(state) {
48
+ var drag = state.projectSplitViewDrag, divider = state.projectSplitViewDividerDrag;
49
+ if (drag) restorePresentationDrag(drag);
50
+ if (divider && divider.frames) divider.frames.forEach(function (item) { if (item.frame.isConnected) item.frame.style.pointerEvents = item.pointerEvents; });
51
+ if (drag || divider) {
52
+ document.body.classList.remove("split-view-resizing");
53
+ var pointerId = drag ? drag.pointerId : divider.pointerId;
54
+ try { if (document.documentElement.hasPointerCapture(pointerId)) document.documentElement.releasePointerCapture(pointerId); } catch (error) {}
55
+ }
56
+ }
57
+ function sendMessage(message) {
58
+ var ws = getWs();
59
+ if (!ws || ws.readyState !== 1 || !projectReady(store.snap())) return false;
60
+ try { ws.send(JSON.stringify(message)); return true; } catch (error) { return false; }
61
+ }
62
+ export function requestProjectSplitViews() {
63
+ var state = store.snap();
64
+ if (!projectReady(state)) return false;
65
+ resetForContext();
66
+ var ws = getWs();
67
+ if (!ws || ws.readyState !== 1) return false;
68
+ store.set({ projectSplitViewRequestSocket: ws, projectSplitViewRequestGeneration: state.projectSplitViewSocketGeneration,
69
+ projectSplitViewRequestOwnerId: state.myUserId || null, projectSplitViewRequestSlug: state.currentSlug });
70
+ try { ws.send(JSON.stringify({ type: "project_split_views_request" })); return true; } catch (error) { return false; }
71
+ }
72
+ function exactProjectMessage(msg) {
73
+ var state = store.snap();
74
+ return !!msg && msg.projectSlug === state.currentSlug && projectReady(state) &&
75
+ state.projectSplitViewRequestSlug === state.currentSlug && state.projectSplitViewRequestGeneration === state.projectSplitViewSocketGeneration &&
76
+ state.projectSplitViewRequestOwnerId === (state.myUserId || null) && state.projectSplitViewRequestSocket === getWs();
77
+ }
78
+ function viewExists(views, id) {
79
+ for (var i = 0; i < (views || []).length; i++) if (views[i].id === id) return views[i];
80
+ return null;
81
+ }
82
+ function viewSessions(view) {
83
+ if (!view || !Array.isArray(view.sessions)) return [];
84
+ var sessions = getCachedSessions() || [], mapped = [];
85
+ for (var i = 0; i < view.sessions.length; i++) {
86
+ var ref = view.sessions[i];
87
+ var session = ref && exactDriverForOrigin(ref.sessionOriginId, sessions);
88
+ if (!session || session.id !== ref.id || !session.sessionOriginId) return [];
89
+ mapped.push(session);
90
+ }
91
+ return mapped;
92
+ }
93
+ export function handleProjectSplitViews(msg) {
94
+ if (!exactProjectMessage(msg)) return false;
95
+ if (!Number.isInteger(msg.revision) || msg.revision < 0 || !Array.isArray(msg.views)) return true;
96
+ var state = store.snap();
97
+ if (state.projectSplitViewHydrated && msg.revision < state.projectSplitViewRevision) return true;
98
+ var nextViews = [];
99
+ var invalidView = false;
100
+ msg.views.forEach(function (view) {
101
+ var ids = view && Array.isArray(view.sessions) ? view.sessions.map(function (session) { return session.sessionOriginId; }) : [];
102
+ if (ids.length < 2 || ids.length > 8 || viewSessions(view).length !== ids.length || !validateTree(view.tree, ids).ok) invalidView = true;
103
+ else nextViews.push(view);
104
+ });
105
+ if (invalidView) {
106
+ store.set({ projectSplitViews: [], projectSplitViewHydrated: false, projectSplitViewError: "Saved Split view references are unavailable. Refresh the session list before changing layouts.",
107
+ projectSplitViewPending: null, projectSplitViewPreviewTree: null, projectSplitViewActiveId: null });
108
+ renderActive();
109
+ return true;
110
+ }
111
+ var pending = state.projectSplitViewPending;
112
+ if (state.projectSplitViewDrag || state.projectSplitViewDividerDrag) cleanupTransientInteractions(state);
113
+ var activeId = state.projectSplitViewActiveId;
114
+ if (!viewExists(nextViews, activeId)) {
115
+ var restored = restorablePresentation(state, nextViews, getCachedSessions());
116
+ if (restored) activeId = restored.id;
117
+ }
118
+ var removedActive = activeId && !viewExists(nextViews, activeId) ? activeId : null;
119
+ var completedPending = pending && pending.awaitingBroadcast && msg.revision >= pending.resultRevision ? pending : null;
120
+ var readyPending = completedPending ? null : pending;
121
+ var standaloneTarget = state.projectSplitViewStandaloneTarget || null;
122
+ if (removedActive && completedPending && completedPending.meta && completedPending.meta.separateViewId === removedActive) {
123
+ var survivor = exactDriverForOrigin(completedPending.meta.activeOrigin, getCachedSessions());
124
+ if (survivor) standaloneTarget = { id: survivor.id, origin: survivor.sessionOriginId, previousSessionId: state.activeSessionId, contextKey: contextKey(state) };
125
+ }
126
+ store.set({ projectSplitViews: nextViews, projectSplitViewRevision: msg.revision, projectSplitViewHydrated: true,
127
+ projectSplitViewError: msg.error || "", projectSplitViewStandaloneTarget: standaloneTarget, projectSplitViewActiveId: viewExists(nextViews, activeId) ? activeId : null,
128
+ projectSplitViewFocusedOrigin: viewExists(nextViews, activeId) ? state.projectSplitViewFocusedOrigin : null,
129
+ projectSplitViewPending: readyPending, projectSplitViewPreviewTree: readyPending ? state.projectSplitViewPreviewTree : null,
130
+ projectSplitViewDrag: null });
131
+ if (removedActive && completedPending && completedPending.meta && completedPending.meta.separateViewId === removedActive) {
132
+ var target = exactDriverForOrigin(completedPending.meta.activeOrigin, getCachedSessions());
133
+ if (target) switchToStandalone(target.id);
134
+ }
135
+ if (completedPending && completedPending.meta && completedPending.meta.activateCreatedView && completedPending.meta.createdViewId &&
136
+ viewExists(nextViews, completedPending.meta.createdViewId)) setActiveView(completedPending.meta.createdViewId);
137
+ if (completedPending && completedPending.meta && completedPending.meta.closedOrigin) {
138
+ var meta = completedPending.meta;
139
+ if (store.get('projectSplitViewFocusedOrigin') === meta.closedOrigin) store.set({ projectSplitViewFocusedOrigin: null });
140
+ var selected = exactDriverForOrigin(meta.closedOrigin, getCachedSessions());
141
+ var remaining = exactDriverForOrigin(meta.activeOrigin, getCachedSessions());
142
+ if (selected && remaining && store.get('activeSessionId') === selected.id && !sendMessage({ type: "switch_session", id: remaining.id })) {
143
+ showToast("Pane removed, but the remaining session could not be selected. Reconnect before continuing.", "error");
144
+ }
145
+ }
146
+ renderActive();
147
+ return true;
148
+ }
149
+ export function handleProjectSplitViewResult(msg) {
150
+ var state = store.snap(), pending = state.projectSplitViewPending;
151
+ if (!pending || !msg || msg.requestId !== pending.requestId ||
152
+ pending.contextKey !== contextKey(state) || pending.socket !== getWs()) return false;
153
+ if (!msg.ok) {
154
+ store.set({ projectSplitViewPending: null, projectSplitViewPreviewTree: null,
155
+ projectSplitViewError: msg.error || "Split view change was not saved" });
156
+ showToast(msg.error || "Split view change was not saved", "error");
157
+ if (msg.code === "conflict" || msg.code === "session" || msg.code === "identity" || msg.code === "folder") {
158
+ store.set({ projectSplitViewHydrated: false });
159
+ requestProjectSplitViews();
160
+ }
161
+ renderActive();
162
+ return true;
163
+ }
164
+ if (!Number.isInteger(msg.revision) || msg.revision < pending.expectedRevision) {
165
+ store.set({ projectSplitViewPending: null, projectSplitViewPreviewTree: null,
166
+ projectSplitViewHydrated: false, projectSplitViewError: "Split view confirmation was incomplete. Refreshing saved state." });
167
+ requestProjectSplitViews();
168
+ renderActive();
169
+ return true;
170
+ }
171
+ var nextMeta = pending.meta;
172
+ if (nextMeta && nextMeta.activateCreatedView && msg.view && msg.view.id) nextMeta = Object.assign({}, nextMeta, { createdViewId: msg.view.id });
173
+ store.set({ projectSplitViewPending: Object.assign({}, pending, { awaitingBroadcast: true, resultRevision: msg.revision, meta: nextMeta }),
174
+ projectSplitViewError: "" });
175
+ return true;
176
+ }
177
+ export function organizePresentationView(id, revision, folderId, favorite) {
178
+ var view = viewExists(store.get('projectSplitViews'), id);
179
+ if (!view || view.revision !== revision) return false;
180
+ return mutation('project_split_view_organize', { id: id, expectedViewRevision: revision, projectSlug: store.get('currentSlug'), folderId: folderId, favorite: favorite });
181
+ }
182
+ function mutation(type, body, meta) {
183
+ var state = store.snap();
184
+ if (!projectReady(state) || !state.projectSplitViewHydrated || state.projectSplitViewError || state.projectSplitViewPending) return false;
185
+ var requestId = "split-view-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
186
+ var ws = getWs();
187
+ var message = Object.assign({ type: type, requestId: requestId, expectedRevision: state.projectSplitViewRevision }, body || {});
188
+ var pending = { requestId: requestId, type: type, socket: ws, contextKey: contextKey(state), expectedRevision: state.projectSplitViewRevision, meta: meta || null,
189
+ awaitingBroadcast: false, resultRevision: null };
190
+ store.set({ projectSplitViewPending: pending, projectSplitViewError: "" });
191
+ if (!sendMessage(message)) {
192
+ store.set({ projectSplitViewPending: null, projectSplitViewError: "Split view change could not be sent. Refresh before trying again." });
193
+ showToast("Split view change could not be sent. Refresh before trying again.", "error");
194
+ return false;
195
+ }
196
+ return true;
197
+ }
198
+ function setActiveView(id, preferredOrigin) {
199
+ var state = store.snap(), view = viewExists(state.projectSplitViews, id);
200
+ if (!view) return false;
201
+ try {
202
+ if (state.splitPanes || state.projectSplitViewActiveId) checkMountedPaneDrafts();
203
+ else checkComposerDeparture();
204
+ } catch (error) { showToast(error.message, "warn"); return false; }
205
+ if (!state.splitPanes && !state.projectSplitViewActiveId) detachTuiView();
206
+ var activeOrigin = preferredOrigin && view.sessions.some(function (session) { return session.sessionOriginId === preferredOrigin; }) ? preferredOrigin : view.activeSessionOriginId;
207
+ var activeSession = viewSessions(view).filter(function (session) { return session.sessionOriginId === activeOrigin; })[0];
208
+ if (activeSession && store.get('activeSessionId') !== activeSession.id) {
209
+ store.set({ projectSplitViewSwitching: true });
210
+ if (!sendMessage({ type: "switch_session", id: activeSession.id })) { store.set({ projectSplitViewSwitching: false }); return false; }
211
+ }
212
+ store.set({ projectSplitViewActiveId: id, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, splitPanes: null });
213
+ renderActive();
214
+ return true;
215
+ }
216
+ function checkMountedPaneDrafts() {
217
+ if (!host) return;
218
+ Array.from(host.querySelectorAll('.split-pane-frame')).forEach(function (frame) { if (frame.clayPane) frame.clayPane.checkDraft(); });
219
+ }
220
+ function switchToStandalone(sessionId) {
221
+ var state = store.snap();
222
+ try {
223
+ if (state.projectSplitViewActiveId || state.splitPanes) checkMountedPaneDrafts();
224
+ else checkComposerDeparture();
225
+ } catch (error) { showToast(error.message, "warn"); return false; }
226
+ if (!sendMessage({ type: "switch_session", id: sessionId })) {
227
+ showToast("Session switch could not be sent. The current Split view is still open.", "error");
228
+ return false;
229
+ }
230
+ store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, projectSplitViewSwitching: false, splitPanes: null });
231
+ renderActive();
232
+ return true;
233
+ }
234
+ function renderActive() {
235
+ if (!host || !nativeApp || preserveReconnectingPresentation(host)) return;
236
+ var state = store.snap(), view = viewExists(state.projectSplitViews, state.projectSplitViewActiveId);
237
+ if (state.splitIdentityBlockedSocket === getWs()) { hidePresentationView(); return; }
238
+ if (!view) {
239
+ var restored = state.projectSplitViewHydrated && restorablePresentation(state, state.projectSplitViews, getCachedSessions());
240
+ if (restored) {
241
+ store.set({ projectSplitViewActiveId: restored.id, projectSplitViewSingletonOrigin: null, projectSplitViewFocusedOrigin: null });
242
+ return;
243
+ }
244
+ var singleton = currentSingletonPresentation(state, getCachedSessions());
245
+ if (singleton) {
246
+ store.set({ projectSplitViewSingletonOrigin: singleton.origin });
247
+ if (stickyContainer && mainPanels) mainPanels.appendChild(stickyContainer);
248
+ renderPresentationView(singleton.view, { id: singleton.origin }, [singleton.session]);
249
+ return;
250
+ }
251
+ if (state.projectSplitViewSingletonOrigin) store.set({ projectSplitViewSingletonOrigin: null });
252
+ hidePresentationView();
253
+ if (stickyContainer && stickyHome) stickyHome.insertBefore(stickyContainer, stickyAnchor && stickyAnchor.parentNode === stickyHome ? stickyAnchor : null);
254
+ return;
255
+ }
256
+ if (state.projectSplitViewSingletonOrigin) store.set({ projectSplitViewSingletonOrigin: null });
257
+ var sessions = viewSessions(view);
258
+ var tree = presentationTree(view);
259
+ var ids = listIds(tree);
260
+ if (sessions.length !== view.sessions.length || ids.length < 2 || ids.length > 8 || !validateTree(tree, ids).ok) return;
261
+ if (stickyContainer && mainPanels) mainPanels.appendChild(stickyContainer);
262
+ renderPresentationView(view, tree, sessions);
263
+ }
264
+ function onClick(event) {
265
+ var target = event.target;
266
+ if (target.closest('.split-view-menu')) return;
267
+ var suppressed = store.get('projectSplitViewSuppressClick');
268
+ if (suppressed && Date.now() <= suppressed.until && event.pointerId === suppressed.pointerId) {
269
+ var clickedRow = target.closest(".session-item[data-session-id], .session-loop-child[data-session-id]"), clickedPane = target.closest(".split-pane");
270
+ var matchesSource = suppressed.sourceElement && (target === suppressed.sourceElement || suppressed.sourceElement.contains(target)) ||
271
+ clickedRow && Number(clickedRow.dataset.sessionId) === suppressed.sourceSessionId ||
272
+ clickedPane && clickedPane.querySelector('.split-pane-frame') && clickedPane.querySelector('.split-pane-frame').dataset.sessionOriginId === suppressed.targetOrigin;
273
+ if (matchesSource) { event.preventDefault(); event.stopImmediatePropagation(); store.set({ projectSplitViewSuppressClick: null }); return; }
274
+ }
275
+ var menu = target.closest(".split-view-arrange"); if (menu) return;
276
+ var splitGroup = target.closest(".split-view-group[data-split-view-id]");
277
+ if (splitGroup) {
278
+ var view = viewExists(store.get('projectSplitViews'), splitGroup.dataset.splitViewId); if (!view) return;
279
+ var more = target.closest(".split-view-group-more");
280
+ if (more) { event.preventDefault(); event.stopImmediatePropagation(); toggleGroupMenu(splitGroup, more); return; }
281
+ var option = target.closest("[data-split-view-action]");
282
+ if (option) { event.preventDefault(); event.stopImmediatePropagation(); runGroupAction(view, option.dataset.splitViewAction, splitGroup); return; }
283
+ var child = target.closest(".session-item[data-session-id], .session-loop-child[data-session-id]");
284
+ if (child && target.closest("button, a, input, textarea, select, [contenteditable='true']")) return;
285
+ var open = target.closest(".split-view-group-open");
286
+ if (open || child) {
287
+ event.preventDefault(); event.stopImmediatePropagation();
288
+ var session = child && exactDriverForOrigin(child.dataset.controlSessionOrigin, getCachedSessions());
289
+ if (child && !session) session = (getCachedSessions() || []).find(function (item) { return item.id === Number(child.dataset.sessionId); });
290
+ if (!setActiveView(view.id, session && session.sessionOriginId)) return;
291
+ if (session && session.sessionOriginId !== view.activeSessionOriginId) updateActiveSession(view, session.sessionOriginId, mutation);
292
+ return;
293
+ }
294
+ }
295
+ if (store.get('projectSplitViewActiveId') && target.closest('.split-group-item[data-split-group-id]')) {
296
+ store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null }); return;
297
+ }
298
+ var row = target.closest(".session-item[data-session-id], .session-loop-child[data-session-id]"); if (row && store.get('projectSplitViewActiveId')) {
299
+ var session = (getCachedSessions() || []).find(function (item) { return item.id === Number(row.dataset.sessionId); });
300
+ var membershipViewId = session && viewIdForOrigin(session.sessionOriginId); if (membershipViewId) {
301
+ event.preventDefault(); event.stopImmediatePropagation(); var membershipView = viewExists(store.get('projectSplitViews'), membershipViewId);
302
+ if (membershipView && setActiveView(membershipView.id, session.sessionOriginId) && session.sessionOriginId !== membershipView.activeSessionOriginId) updateActiveSession(membershipView, session.sessionOriginId, mutation);
303
+ return;
304
+ }
305
+ if (session && (session.sessionRole === "worker" || groupedSessionIds(store.get('splitGroups') || []).has(session.id))) {
306
+ store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null });
307
+ return;
308
+ }
309
+ if (session) { event.preventDefault(); event.stopImmediatePropagation(); switchToStandalone(session.id); }
310
+ }
311
+ if (!target.closest(".split-view-group-more")) {
312
+ var groupFocusTarget = target.closest("button, a, input, textarea, select, [tabindex]:not([tabindex='-1'])");
313
+ closeGroupMenus(!groupFocusTarget);
314
+ }
315
+ if (!menu) {
316
+ var arrangeFocusTarget = target.closest("button, a, input, textarea, select, [tabindex]:not([tabindex='-1'])");
317
+ closePresentationArrange(!arrangeFocusTarget);
318
+ }
319
+ }
320
+ function onContextMenu(event) {
321
+ var target = event.target;
322
+ if (!target || !target.closest) return;
323
+ var heading = target.closest('.split-view-group-heading'), group = heading && heading.closest('.split-view-group[data-split-view-id]');
324
+ if (!group || target.closest('input, textarea, [contenteditable="true"]') ||
325
+ !projectReady(store.snap()) || !viewExists(store.get('projectSplitViews'), group.dataset.splitViewId)) return;
326
+ var anchor = group.querySelector('.split-view-group-more');
327
+ if (!anchor) return;
328
+ event.preventDefault(); event.stopImmediatePropagation();
329
+ closeSessionContextMenu(false); closePresentationArrange(false);
330
+ toggleGroupMenu(group, anchor, event.clientX || event.clientY ? { x: event.clientX, y: event.clientY } : null);
331
+ }
332
+ function toggleGroupMenu(group, anchor, point) {
333
+ openSplitViewMenu(group, anchor, point, runGroupAction);
334
+ }
335
+ function runGroupAction(view, action, root) {
336
+ closeGroupMenus(false);
337
+ if (action === 'favorite' || action === 'folder') { organizeViewAction(view, action); return; }
338
+ if (action === "rename") { startRename(root, view); return; }
339
+ if (action === "separate") {
340
+ if (view.id === store.get('projectSplitViewActiveId')) {
341
+ try { checkMountedPaneDrafts(); } catch (error) { showToast(error.message, "warn"); return; }
342
+ }
343
+ var selectedOrigin = view.activeSessionOriginId;
344
+ mutation("project_split_view_separate", { id: view.id }, { separateViewId: view.id, activeOrigin: selectedOrigin });
345
+ }
346
+ }
347
+ function startRename(root, view) {
348
+ var name = root.querySelector('.split-view-group-name'), heading = root.querySelector('.split-view-group-heading');
349
+ if (!name || !heading || root.querySelector('.split-view-group-name-input')) return;
350
+ var input = document.createElement("input"); input.type = "text"; input.className = "split-view-group-name-input"; input.maxLength = 80;
351
+ input.value = view.name; input.setAttribute("aria-label", "Rename Split view"); name.replaceWith(input); input.focus(); input.select();
352
+ var finished = false;
353
+ function restore(returnFocus) {
354
+ if (finished) return;
355
+ finished = true;
356
+ if (!input.isConnected) return;
357
+ var span = document.createElement("span"); span.className = "split-view-group-name"; span.textContent = view.name; input.replaceWith(span);
358
+ if (returnFocus) { var trigger = root.querySelector('.split-view-group-open'); if (trigger) trigger.focus(); }
359
+ }
360
+ function showInlineError(message) {
361
+ var error = root.querySelector('.split-view-group-error');
362
+ if (!error) return;
363
+ error.textContent = message;
364
+ error.hidden = false;
365
+ input.setAttribute("aria-invalid", "true");
366
+ input.removeAttribute("aria-describedby");
367
+ }
368
+ input.addEventListener("click", function (event) { event.stopPropagation(); });
369
+ var inlineError = root.querySelector('.split-view-group-error');
370
+ if (inlineError) inlineError.hidden = true;
371
+ input.addEventListener("keydown", function (event) {
372
+ if (event.key === "Escape") { event.preventDefault(); restore(true); return; }
373
+ if (event.key === "Enter") {
374
+ event.preventDefault(); var next = input.value.trim();
375
+ if (!next || next.length > 80) { showInlineError("Use a name from 1 to 80 characters."); return; }
376
+ input.removeAttribute("aria-invalid");
377
+ var currentError = root.querySelector('.split-view-group-error'); if (currentError) currentError.hidden = true;
378
+ if (!mutation("project_split_view_rename", { id: view.id, name: next })) { showInlineError(store.get('projectSplitViewError') || "Rename could not be saved."); return; }
379
+ restore();
380
+ }
381
+ });
382
+ input.addEventListener("blur", restore);
383
+ }
384
+ function onKeyDown(event) {
385
+ if (event.key === "Escape") closePresentationArrange();
386
+ }
387
+ function onArrangement(event) {
388
+ var view = viewExists(store.get('projectSplitViews'), store.get('projectSplitViewActiveId'));
389
+ if (!view || !event.detail) return;
390
+ var source = view.sessions.find(function (session) { return session.sessionOriginId === event.detail.sessionOriginId; });
391
+ if (!source || !exactDriverForOrigin(source.sessionOriginId, getCachedSessions())) return;
392
+ var nextTree = null, nextLayout = view.layout;
393
+ if (event.detail.preset) {
394
+ nextLayout = event.detail.preset;
395
+ var orderedIds = [source.sessionOriginId].concat(listIds(view.tree).filter(function (origin) { return origin !== source.sessionOriginId; }));
396
+ nextTree = preset(orderedIds, nextLayout);
397
+ }
398
+ else if (event.detail.position) {
399
+ var arranged = arrangeRegion(view.tree, source.sessionOriginId, event.detail.position);
400
+ if (!arranged.ok) { showToast(arranged.reason === "quarter-requires-three" ? "Quarter layouts require at least three sessions." : "This arrangement is unavailable.", "warn"); return; }
401
+ nextTree = arranged.tree;
402
+ }
403
+ if (!nextTree || !validateTree(nextTree, view.sessions.map(function (session) { return session.sessionOriginId; })).ok) return;
404
+ store.set({ projectSplitViewPreviewTree: nextTree, projectSplitViewPreviewScope: layoutScope(view), projectSplitViewFocusedOrigin: null }); renderActive();
405
+ if (!mutation("project_split_view_update", { id: view.id, sessionOrigins: listIds(nextTree), tree: nextTree,
406
+ layout: nextLayout, activeSessionOriginId: source.sessionOriginId })) {
407
+ store.set({ projectSplitViewPreviewTree: null }); renderActive();
408
+ }
409
+ }
410
+ function onResize(event) {
411
+ if (!event.detail || !currentLayoutScope(event.detail.scope) || event.detail.viewId !== event.detail.scope.viewId) return;
412
+ var view = viewExists(store.get('projectSplitViews'), event.detail.viewId);
413
+ if (!view) return;
414
+ if (store.get('projectSplitViewPending') || !mutation("project_split_view_update", { id: view.id, sessionOrigins: event.detail.ids, tree: event.detail.tree,
415
+ layout: view.layout, activeSessionOriginId: view.activeSessionOriginId })) { store.set({ projectSplitViewPreviewTree: null }); renderActive(); }
416
+ }
417
+ function onClosePane(event) {
418
+ var view = viewExists(store.get('projectSplitViews'), store.get('projectSplitViewActiveId'));
419
+ if (!view || !event.detail || !validPresentationClose(event.detail)) return;
420
+ var session = (getCachedSessions() || []).find(function (item) { return item.id === Number(event.detail.sessionId); });
421
+ if (!session || !view.sessions.some(function (item) { return item.sessionOriginId === session.sessionOriginId; })) return;
422
+ try { checkMountedPaneDrafts(); } catch (error) { showToast(error.message, "warn"); return; }
423
+ var origins = view.sessions.map(function (item) { return item.sessionOriginId; }).filter(function (origin) { return origin !== session.sessionOriginId; });
424
+ if (origins.length < 2) {
425
+ var remaining = view.sessions.filter(function (item) { return item.sessionOriginId !== session.sessionOriginId; })[0];
426
+ if (remaining) mutation("project_split_view_separate", { id: view.id }, { separateViewId: view.id, activeOrigin: remaining.sessionOriginId });
427
+ return;
428
+ }
429
+ var nextTree = remove(view.tree, session.sessionOriginId);
430
+ if (!nextTree || !validateTree(nextTree, origins).ok) return;
431
+ var nextActive = view.activeSessionOriginId === session.sessionOriginId ? origins[0] : view.activeSessionOriginId;
432
+ mutation("project_split_view_update", { id: view.id, sessionOrigins: origins, tree: nextTree,
433
+ layout: view.layout, activeSessionOriginId: nextActive }, { closedOrigin: session.sessionOriginId, activeOrigin: nextActive });
434
+ }
435
+ function onFocus(event) {
436
+ var view = viewExists(store.get('projectSplitViews'), store.get('projectSplitViewActiveId'));
437
+ if (!view) return;
438
+ var source = event.detail && view.sessions.find(function (session) { return session.sessionOriginId === event.detail.sessionOriginId; });
439
+ if (event.detail && event.detail.focused && !source) return;
440
+ store.set({ projectSplitViewFocusedOrigin: event.detail && event.detail.focused ? source.sessionOriginId : null });
441
+ renderActive();
442
+ }
443
+ function init(hostElement) {
444
+ host = hostElement; nativeApp = document.getElementById("app"); mainPanels = document.getElementById("main-panels");
445
+ stickyContainer = document.getElementById("sticky-notes-container");
446
+ if (stickyContainer) { stickyHome = stickyContainer.parentNode; stickyAnchor = stickyContainer.nextSibling; }
447
+ initPresentationRenderer(host);
448
+ initPresentationDrag(host, function (drag, target) { applyPresentationDrop(drag, target, mutation, checkMountedPaneDrafts); });
449
+ initSplitViewCreateMenu();
450
+ document.addEventListener('clay-split-view-create', function (event) { handleSplitViewCreateEvent(event, mutation, checkMountedPaneDrafts); });
451
+ document.addEventListener("click", onClick, true);
452
+ document.addEventListener("contextmenu", onContextMenu, true);
453
+ document.addEventListener("clay-split-view-arrange", onArrangement);
454
+ document.addEventListener("clay-split-view-resize", onResize);
455
+ document.addEventListener("clay-split-view-focus", onFocus);
456
+ document.addEventListener("clay-split-view-close-pane", onClosePane);
457
+ document.addEventListener("keydown", onKeyDown, true);
458
+ store.subscribe(function (state, previous) {
459
+ if ((previous.projectSplitViewDrag && !state.projectSplitViewDrag) ||
460
+ (previous.projectSplitViewDividerDrag && !state.projectSplitViewDividerDrag)) cleanupTransientInteractions(previous);
461
+ if (state.projectSplitViewStandaloneTarget && state.activeSessionId !== previous.activeSessionId) store.set({ projectSplitViewStandaloneTarget: null });
462
+ var contextChanged = resetForContext();
463
+ if (previous.connected && !state.connected) {
464
+ var pending = state.projectSplitViewPending;
465
+ store.set({ projectSplitViews: [], projectSplitViewRevision: 0, projectSplitViewHydrated: false,
466
+ projectSplitViewPending: null, projectSplitViewError: pending ? "Connection changed during a Split view change. Saved state will be refreshed after reconnect." : "",
467
+ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null, projectSplitViewRequestSocket: null });
468
+ }
469
+ var becameReady = projectReady(state) && (!projectReady(previous) || contextChanged);
470
+ if (becameReady) requestProjectSplitViews();
471
+ if (state.projectSplitViews !== previous.projectSplitViews || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.activeSessionId !== previous.activeSessionId || state.sessionOriginId !== previous.sessionOriginId || state.splitPanes !== previous.splitPanes ||
472
+ state.projectSplitViewPreviewTree !== previous.projectSplitViewPreviewTree || state.currentSlug !== previous.currentSlug ||
473
+ state.paneBindingSessions !== previous.paneBindingSessions || state.sessionActivatedProjectSlug !== previous.sessionActivatedProjectSlug) renderActive();
474
+ if (state.activeSessionId !== previous.activeSessionId && state.projectSplitViewActiveId) {
475
+ var activeView = viewExists(state.projectSplitViews, state.projectSplitViewActiveId);
476
+ if (activeView && !activeView.sessions.some(function (session) { return session.id === state.activeSessionId; })) {
477
+ store.set({ projectSplitViewActiveId: null, projectSplitViewPreviewTree: null });
478
+ }
479
+ }
480
+ });
481
+ var changed = resetForContext();
482
+ if (changed && projectReady(store.snap())) requestProjectSplitViews();
483
+ }
484
+ export function initSplitViewPresentations(hostElement) { init(hostElement); }
485
+ export function renderPresentationSplitView() { renderActive(); }
486
+ export function presentationViewForSidebar(viewId) { return viewExists(store.get('projectSplitViews'), viewId); }
487
+
488
+ // Canonical navigation uses the same draft guard and view renderer as human entry.
489
+ export function applyReviewedSplitView(target) {
490
+ var view = viewExists(store.get('projectSplitViews'), target.view.id);
491
+ if (!projectReady(store.snap()) || store.get('projectSplitViewPending') || !view || JSON.stringify(view) !== JSON.stringify(target.view)) return false;
492
+ if (!setActiveView(view.id, view.activeSessionOriginId)) return false;
493
+ onFocus({ detail: { focused: !!target.focusOrigin, sessionOriginId: target.focusOrigin } });
494
+ return true;
495
+ }
@@ -0,0 +1,28 @@
1
+ import { toggleSidebarCollapse } from './sidebar.js';
2
+
3
+ export function syncSidebarReopenButton(button, active) {
4
+ if (!button) return;
5
+ var layout = document.getElementById("layout");
6
+ var desktop = !window.matchMedia || window.matchMedia("(min-width: 769px)").matches;
7
+ button.hidden = !layout || !desktop || !active || !layout.classList.contains("sidebar-collapsed");
8
+ }
9
+
10
+ export function createSidebarReopenButton() {
11
+ var target = document.querySelector("#top-bar .top-bar-left-pills");
12
+ if (!target) return null;
13
+ var button = document.createElement("button");
14
+ button.id = "sidebar-shell-expand-btn";
15
+ button.type = "button";
16
+ button.title = "Open sidebar";
17
+ button.setAttribute("aria-label", "Open sidebar");
18
+ button.setAttribute("aria-controls", "sidebar-column");
19
+ button.setAttribute("aria-expanded", "false");
20
+ button.innerHTML = '<i data-lucide="panel-left-open"></i>';
21
+ button.hidden = true;
22
+ button.addEventListener("click", function () {
23
+ if (button.hidden) return;
24
+ toggleSidebarCollapse();
25
+ });
26
+ target.prepend(button);
27
+ return button;
28
+ }
@@ -0,0 +1,42 @@
1
+ import { getWs } from './ws-ref.js';
2
+ import { store } from './store.js';
3
+ import { iconHtml, refreshIcons } from './icons.js';
4
+ import { closeSessionContextMenu, mountSessionContextMenu, positionSessionContextMenu } from './session-context-menu.js';
5
+ import { closeFolderMenu } from './session-folder-context.js';
6
+
7
+ export function closeSplitViewMenu(returnFocus) {
8
+ var current = store.get('sessionContextMenu');
9
+ if (current && current.el.classList.contains('split-view-menu')) closeSessionContextMenu(returnFocus);
10
+ }
11
+
12
+ export function openSplitViewMenu(group, anchor, point, onAction) {
13
+ var view = (store.get('projectSplitViews') || []).find(function (item) { return item.id === group.dataset.splitViewId; });
14
+ if (!view) return;
15
+ var surface = group.dataset.splitViewSurface || 'desktop';
16
+ var socket = getWs(), generation = store.get('projectSplitViewSocketGeneration');
17
+ var slug = store.get('currentSlug'), viewer = store.get('myUserId'), projection = group.dataset.splitViewProjection;
18
+ function currentRoot() {
19
+ var live = (store.get('projectSplitViews') || []).find(function (item) { return item.id === view.id && item.revision === view.revision && item.name === view.name; });
20
+ if (!live || socket !== getWs() || generation !== store.get('projectSplitViewSocketGeneration') || slug !== store.get('currentSlug') || viewer !== store.get('myUserId') || !store.get('connected')) return null;
21
+ function matches(root) { return root.isConnected && root.dataset.splitViewId === view.id && root.dataset.splitViewProjection === projection && (root.dataset.splitViewSurface || 'desktop') === surface; }
22
+ if (matches(group)) return group;
23
+ var roots = Array.from(document.querySelectorAll('.split-view-group[data-split-view-id]')).filter(matches);
24
+ return roots.length === 1 ? roots[0] : null;
25
+ }
26
+ closeFolderMenu(false); closeSessionContextMenu(false);
27
+ var menu = document.createElement('div'); menu.className = 'session-ctx-menu split-view-menu';
28
+ [['favorite', view.favorite ? 'Remove from Favorites' : 'Add to Favorites', 'star'], ['folder', 'Move to folder', 'folder-input'], ['rename', 'Rename', 'pencil'], ['separate', 'Separate', 'columns-2']].forEach(function (entry) {
29
+ var button = document.createElement('button'); button.className = 'session-ctx-item'; button.dataset.splitViewAction = entry[0];
30
+ button.innerHTML = iconHtml(entry[2]) + ' <span>' + entry[1] + '</span>';
31
+ button.addEventListener('click', function (event) {
32
+ event.stopPropagation(); var root = currentRoot(); closeSessionContextMenu(false);
33
+ if (root) onAction(view, entry[0], root);
34
+ });
35
+ menu.appendChild(button);
36
+ });
37
+ document.body.appendChild(menu);
38
+ mountSessionContextMenu(menu, anchor, null, { resolveAnchor: function () {
39
+ var root = currentRoot(); return root && root.querySelector('.split-view-group-more');
40
+ } });
41
+ refreshIcons(); positionSessionContextMenu(menu, anchor, point);
42
+ }
@@ -0,0 +1,30 @@
1
+ import { store } from './store.js';
2
+ import { appendViewOrganization } from './split-view-organization.js';
3
+ import { iconHtml } from './icons.js';
4
+
5
+ export function renderPresentationSplitViewItem(view, members, renderSessionItem, containerId, surface) {
6
+ var root = document.createElement("div"), activeId = store.get('projectSplitViewActiveId');
7
+ var fullName = view.nameMode === "automatic" ? members.map(function (session) { return session.title || "Session"; }).join(" + ") : view.name;
8
+ var nameCharacters = Array.from(fullName);
9
+ var displayName = nameCharacters.length > 80 ? nameCharacters.slice(0, 79).join("") + "…" : fullName;
10
+ root.className = "split-view-group"; root.dataset.splitViewId = view.id; root.dataset.splitViewProjection = containerId || 'unfiled'; root.dataset.splitViewSurface = surface || 'desktop';
11
+ root.dataset.active = activeId === view.id ? "true" : "false"; root.title = fullName;
12
+ var heading = document.createElement("div"); heading.className = "split-view-group-heading";
13
+ var open = document.createElement("button"); open.type = "button"; open.className = "split-view-group-open";
14
+ open.setAttribute("aria-expanded", activeId === view.id ? "true" : "false");
15
+ open.setAttribute("aria-label", "Open Split view " + fullName); open.title = fullName;
16
+ open.innerHTML = iconHtml("columns-2");
17
+ var name = document.createElement("span"); name.className = "split-view-group-name"; name.textContent = displayName; open.appendChild(name);
18
+ var count = document.createElement("span"); count.className = "split-view-group-count"; count.textContent = String(members.length); open.appendChild(count);
19
+ heading.appendChild(open);
20
+ appendViewOrganization(heading, view);
21
+ var more = document.createElement("button"); more.type = "button"; more.className = "split-view-group-more";
22
+ more.setAttribute("aria-label", "Split view options " + fullName); more.setAttribute("aria-haspopup", "menu"); more.setAttribute("aria-expanded", "false");
23
+ more.innerHTML = iconHtml("ellipsis"); heading.appendChild(more); root.appendChild(heading);
24
+ var children = document.createElement("div"); children.className = "split-view-group-children";
25
+ for (var i = 0; containerId !== 'favorites' && i < members.length; i++) children.appendChild(renderSessionItem(members[i]));
26
+ root.appendChild(children);
27
+ var error = document.createElement("div"); error.className = "split-view-group-error";
28
+ error.textContent = store.get('projectSplitViewError') || ""; error.hidden = !error.textContent; root.appendChild(error);
29
+ return root;
30
+ }
@@ -0,0 +1,21 @@
1
+ import { getWs } from './ws-ref.js';
2
+ // Build the transient one-Driver pane model without creating saved view state.
3
+ export function singletonPresentation(state, sessions) {
4
+ if (!state || state.splitPanes) return null;
5
+ var target = state.projectSplitViewStandaloneTarget;
6
+ var validTarget = target && target.previousSessionId === state.activeSessionId && target.contextKey === state.projectSplitViewContextKey;
7
+ var candidate = (sessions || []).find(function (session) {
8
+ return validTarget ? session.id === target.id && session.sessionOriginId === target.origin && (session.ownerId || null) === (state.myUserId || null) : session.id === state.activeSessionId;
9
+ });
10
+ if (!candidate || !candidate.sessionOriginId || candidate.sessionRole === "worker" || candidate.isMate === true || candidate.isTui === true ||
11
+ (candidate.runtimeMode || candidate.mode || "gui") !== "gui") return null;
12
+ return { origin: candidate.sessionOriginId, view: { id: "single:" + candidate.sessionOriginId,
13
+ sessions: [candidate], activeSessionOriginId: candidate.sessionOriginId }, session: candidate };
14
+ }
15
+
16
+ export function currentSingletonPresentation(state, sessions) {
17
+ if (!state || state.splitIdentityBlockedSocket && state.splitIdentityBlockedSocket === getWs() || state.paneMode || state.dmMode || !state.currentSlug || state.activeProjectSlug !== state.currentSlug ||
18
+ state.sessionListProjectSlug !== state.currentSlug || !state.authIdentityLoaded || !state.connected ||
19
+ state.projectSplitViewContextKey !== [state.currentSlug || "", state.currentProjectOwnerId || "", state.myUserId || "", state.projectSplitViewSocketGeneration || 0].join("|")) return null;
20
+ return singletonPresentation(state, sessions);
21
+ }