clay-server 5.3.0-beta.4 → 5.3.0-beta.6

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 (144) hide show
  1. package/lib/ask-user-request-identity.js +18 -1
  2. package/lib/clay-product-control-tools.js +2 -2
  3. package/lib/clay-product-control.js +1 -1
  4. package/lib/clay-screen-controls.js +1 -0
  5. package/lib/clay-split-view-awareness.js +5 -2
  6. package/lib/clay-split-view-controls.js +14 -7
  7. package/lib/clay-split-view-input.js +5 -4
  8. package/lib/clay-split-view-render-proof.js +14 -4
  9. package/lib/clay-view-workspace-input.js +71 -0
  10. package/lib/project-delegated-follow-up.js +18 -9
  11. package/lib/project-file-history.js +11 -9
  12. package/lib/project-file-watch.js +26 -7
  13. package/lib/project-filesystem.js +5 -4
  14. package/lib/project-shared-browser.js +7 -3
  15. package/lib/project-split-view-document.js +16 -2
  16. package/lib/project-split-views.js +15 -294
  17. package/lib/project-user-message.js +4 -3
  18. package/lib/project-vendor-login.js +2 -2
  19. package/lib/project-view-input.js +11 -0
  20. package/lib/project-view-pair.js +26 -0
  21. package/lib/project-view-store.js +346 -0
  22. package/lib/project-view-terminal-binding.js +16 -0
  23. package/lib/project-view-workspace.js +126 -0
  24. package/lib/project.js +1 -0
  25. package/lib/public/app.js +2 -0
  26. package/lib/public/css/issues.css +3 -0
  27. package/lib/public/css/pane.css +5 -1
  28. package/lib/public/css/right-workbench.css +25 -3
  29. package/lib/public/css/scheduled-tasks.css +7 -0
  30. package/lib/public/css/shared-browser.css +21 -0
  31. package/lib/public/css/split-view-layout.css +37 -3
  32. package/lib/public/css/sticky-notes.css +3 -2
  33. package/lib/public/css/tool-pane.css +127 -0
  34. package/lib/public/index.html +3 -3
  35. package/lib/public/modules/app-header.js +24 -2
  36. package/lib/public/modules/app-messages.js +1 -1
  37. package/lib/public/modules/clay-file-links.js +2 -1
  38. package/lib/public/modules/clay-session-links.js +1 -1
  39. package/lib/public/modules/clay-split-view-screen.js +6 -3
  40. package/lib/public/modules/clay-tool-pane-screen.js +35 -0
  41. package/lib/public/modules/file-history-snapshot.js +12 -0
  42. package/lib/public/modules/file-pane-entrypoints.js +55 -0
  43. package/lib/public/modules/file-pane-history.js +78 -0
  44. package/lib/public/modules/file-pane-instances.js +140 -0
  45. package/lib/public/modules/file-pane-links.js +41 -0
  46. package/lib/public/modules/file-pane-render.js +48 -0
  47. package/lib/public/modules/file-pane-review.js +26 -0
  48. package/lib/public/modules/file-pane-toolbar.js +54 -0
  49. package/lib/public/modules/file-viewer-source.js +70 -0
  50. package/lib/public/modules/filebrowser.js +44 -83
  51. package/lib/public/modules/git-panel.js +5 -0
  52. package/lib/public/modules/issues.js +3 -4
  53. package/lib/public/modules/linear-panel.js +2 -2
  54. package/lib/public/modules/mate-knowledge-workbench.js +10 -6
  55. package/lib/public/modules/notifications.js +4 -1
  56. package/lib/public/modules/pane-activation.js +5 -0
  57. package/lib/public/modules/pane-binding.js +5 -1
  58. package/lib/public/modules/pane-browser-bridge.js +20 -6
  59. package/lib/public/modules/pane-browser-transport.js +110 -0
  60. package/lib/public/modules/pane-header-runtime.js +18 -0
  61. package/lib/public/modules/plantuml-preview.js +12 -12
  62. package/lib/public/modules/right-workbench.js +116 -67
  63. package/lib/public/modules/scheduled-tasks.js +7 -6
  64. package/lib/public/modules/shared-browser.js +75 -9
  65. package/lib/public/modules/split-pane-renderer.js +7 -8
  66. package/lib/public/modules/split-pane-runtime.js +23 -0
  67. package/lib/public/modules/split-view-drag-preview.js +90 -0
  68. package/lib/public/modules/split-view-layout-client.js +1 -1
  69. package/lib/public/modules/split-view-presentation-drag.js +36 -13
  70. package/lib/public/modules/split-view-presentation-drop.js +21 -18
  71. package/lib/public/modules/split-view-presentation-renderer.js +15 -10
  72. package/lib/public/modules/split-view-presentations.js +25 -33
  73. package/lib/public/modules/split-view.js +17 -16
  74. package/lib/public/modules/sticky-notes-browser.js +2 -2
  75. package/lib/public/modules/sticky-notes.js +8 -10
  76. package/lib/public/modules/terminal.js +45 -13
  77. package/lib/public/modules/theme.js +1 -1
  78. package/lib/public/modules/tool-pane-chrome.js +82 -0
  79. package/lib/public/modules/tool-pane-drag.js +134 -0
  80. package/lib/public/modules/tool-pane-layout.js +95 -0
  81. package/lib/public/modules/tool-pane-legacy.js +60 -0
  82. package/lib/public/modules/tool-pane-tree.js +51 -0
  83. package/lib/public/modules/view-legacy-coordinates.js +16 -0
  84. package/lib/public/modules/view-mutations.js +24 -0
  85. package/lib/public/modules/view-pair-presentation.js +74 -0
  86. package/lib/public/modules/view-pair-validation.js +12 -0
  87. package/lib/public/modules/view-presentation-state.js +37 -0
  88. package/lib/public/modules/view-workspace-controller.js +210 -0
  89. package/lib/public/modules/view-workspace-state.js +103 -0
  90. package/lib/public/modules/view-workspace-validation.js +47 -0
  91. package/lib/public/modules/wireframe-workbench.js +11 -0
  92. package/lib/public/modules/workbench-layout-preference.js +12 -5
  93. package/lib/public/modules/workbench-panels.js +21 -0
  94. package/lib/public/modules/workspace-assignment-card.js +9 -6
  95. package/lib/public/pane-assets/{app-ED2VCGNQ.js → app-7UZ727NA.js} +1 -1
  96. package/lib/public/pane-assets/chunk-2AIWCI6Y.js +1 -0
  97. package/lib/public/pane-assets/chunk-6MIHIQ2W.js +1 -0
  98. package/lib/public/pane-assets/chunk-6NYCNWVF.js +1 -0
  99. package/lib/public/pane-assets/chunk-6QFLYOVY.js +1 -0
  100. package/lib/public/pane-assets/chunk-6UJ2ALOB.js +1 -0
  101. package/lib/public/pane-assets/chunk-6XK2OATZ.js +1 -0
  102. package/lib/public/pane-assets/chunk-BKIDR2ME.js +1 -0
  103. package/lib/public/pane-assets/chunk-BMHTBRMO.js +1 -0
  104. package/lib/public/pane-assets/chunk-GEFCFZEN.js +1 -0
  105. package/lib/public/pane-assets/chunk-GN2NQEVC.js +2 -0
  106. package/lib/public/pane-assets/chunk-IMLMBPT4.js +166 -0
  107. package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
  108. package/lib/public/pane-assets/chunk-K5S6AILM.js +1 -0
  109. package/lib/public/pane-assets/chunk-LDIZLKB2.js +1 -0
  110. package/lib/public/pane-assets/chunk-OWNNLZ36.js +1 -0
  111. package/lib/public/pane-assets/chunk-P2C3LOWL.js +3 -0
  112. package/lib/public/pane-assets/chunk-PV4PCH2V.js +2 -0
  113. package/lib/public/pane-assets/chunk-R4LMPTKU.js +1 -0
  114. package/lib/public/pane-assets/chunk-R7QVVFUG.js +1 -0
  115. package/lib/public/pane-assets/chunk-TMGSDFJI.js +1 -0
  116. package/lib/public/pane-assets/chunk-VKYBAVWT.js +1 -0
  117. package/lib/public/pane-assets/chunk-VNAV23CM.js +1 -0
  118. package/lib/public/pane-assets/chunk-VWTHIQCE.js +1 -0
  119. package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
  120. package/lib/public/pane-assets/chunk-WM3K5AFH.js +1 -0
  121. package/lib/public/pane-assets/chunk-XOB3O327.js +1 -0
  122. package/lib/public/pane-assets/chunk-XOOUCFYT.js +1 -0
  123. package/lib/public/pane-assets/manifest.json +383 -336
  124. package/lib/public/pane-assets/style-ZM7OOU3M.css +1 -0
  125. package/lib/public/style.css +1 -0
  126. package/lib/server-workbench-layout-settings.js +3 -3
  127. package/lib/server.js +1 -0
  128. package/lib/split-view-layout.js +11 -7
  129. package/lib/users-workbench-layout-preferences.js +14 -4
  130. package/lib/workbench-window-layout.js +30 -0
  131. package/lib/workspace-assignment-service.js +56 -11
  132. package/lib/workspace-follow-up-queue.js +92 -0
  133. package/lib/workspace-query-mcp-server.js +1 -1
  134. package/package.json +1 -1
  135. package/lib/public/pane-assets/chunk-6D4DPE5X.js +0 -1
  136. package/lib/public/pane-assets/chunk-6SM2WQJX.js +0 -1
  137. package/lib/public/pane-assets/chunk-7H36SUZA.js +0 -2
  138. package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
  139. package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
  140. package/lib/public/pane-assets/chunk-IYFILQHZ.js +0 -2
  141. package/lib/public/pane-assets/chunk-IZ2ZANEY.js +0 -167
  142. package/lib/public/pane-assets/chunk-JAEQCN5S.js +0 -1
  143. package/lib/public/pane-assets/chunk-ST2Q62ET.js +0 -1
  144. package/lib/public/pane-assets/style-JVCOCG3W.css +0 -1
@@ -1,3 +1,5 @@
1
+ import { syncPairView, pairPresentationPanes, pairViewCapacity } from './view-pair-presentation.js';
2
+ import { paintLegacyToolPanes } from './tool-pane-legacy.js';
1
3
  import { adoptDocumentSource } from './clay-studio-document.js';
2
4
  import { trustedStudioFrame } from './clay-studio-render.js';
3
5
  import { handlePaneBrowserMessage } from './pane-browser-bridge.js';
@@ -131,21 +133,15 @@ function closePane(sessionId) {
131
133
  // Called on session_list so pane headers follow renames made elsewhere
132
134
  // (sidebar, in-pane app). Panes being renamed inline are left alone.
133
135
  export function syncPaneTitles() {
134
- var split = store.get('splitPanes');
135
- if (!split || !split.panes || !host) return;
136
- var titleEls = host.querySelectorAll(".split-pane-title");
137
- var paneEls = host.querySelectorAll('.split-pane');
138
- for (var i = 0; i < split.panes.length && i < titleEls.length; i++) {
139
- var session = sessionById(paneEls[i] && Number(paneEls[i].dataset.sessionId) || split.panes[i].sessionId);
140
- if (!session || !session.title) continue;
141
- if (titleEls[i].parentNode) titleEls[i].parentNode.title = session.title;
142
- if (titleEls[i].style.display !== "none") titleEls[i].textContent = session.title;
143
- }
144
- var accessBtns = host.querySelectorAll(".split-pane-full-access");
145
- for (var ai = 0; ai < split.panes.length && ai < accessBtns.length; ai++) {
146
- updatePaneFullAccessButton(accessBtns[ai], sessionById(paneEls[ai] && Number(paneEls[ai].dataset.sessionId) || split.panes[ai].sessionId));
136
+ if (!host) return;
137
+ var panes = host.querySelectorAll('.split-pane:not(.split-pane-cached)');
138
+ for (var i = 0; i < panes.length; i++) {
139
+ var frame = panes[i].querySelector('.split-pane-frame');
140
+ if (frame && frame.dataset.requestedSessionId) continue;
141
+ var session = sessionById(Number(panes[i].dataset.sessionId));
142
+ if (session) updateSplitPaneMetadata(panes[i], { sessionId: session.id, slug: panes[i].dataset.projectSlug, title: session.title });
147
143
  }
148
- syncPairChrome(host, split);
144
+ syncPairChrome(host, store.get('splitPanes'));
149
145
  }
150
146
 
151
147
  function handlePaneMessage(event) {
@@ -191,7 +187,8 @@ function renderSplit(split) {
191
187
  singleton || split && split.panes && split.panes.length >= 2 || !!state.projectSplitViewActiveId);
192
188
  // The exact singleton presentation is verified before the redundant cache catches up.
193
189
  if (singleton) { renderPresentationSplitView(); return; }
194
- if (split && split.panes && store.get('projectSplitViewActiveId')) {
190
+ var savedPair = split && split.panes && syncPairView();
191
+ if (split && split.panes && !savedPair && store.get('projectSplitViewActiveId')) {
195
192
  store.set({ projectSplitViewActiveId: null, projectSplitViewFocusedOrigin: null, projectSplitViewPreviewTree: null });
196
193
  }
197
194
  var expectedPresentationContext = [store.get('currentSlug') || "", store.get('currentProjectOwnerId') || "", store.get('myUserId') || "", store.get('projectSplitViewSocketGeneration') || 0].join("|");
@@ -219,8 +216,10 @@ function renderSplit(split) {
219
216
  placeStickyNotesOverlay(true);
220
217
  nativeApp.classList.add("split-native-hidden");
221
218
  host.classList.add("visible");
219
+ if (savedPair) panes = pairPresentationPanes(panes);
222
220
  reconcileSplitPanes(host, panes, function (pane) { return createSplitPane(pane, closePane); }, updateSplitPaneMetadata);
223
221
  syncPairChrome(host, split);
222
+ paintLegacyToolPanes(host);
224
223
  refreshIcons();
225
224
  }
226
225
 
@@ -252,7 +251,7 @@ function openSplit(side, draggedId) {
252
251
 
253
252
  export function openGroup(group) {
254
253
  if (!canLeavePanes()) return false;
255
- if (!group || !Array.isArray(group.members) || group.members.length < 2) return false;
254
+ if (!group || !Array.isArray(group.members) || group.members.length < 2 || !pairViewCapacity(group)) return false;
256
255
  var memberIds = splitGroupMemberIds(group);
257
256
  if (memberIds.length !== group.members.length) return false;
258
257
  for (var mi = 0; mi < memberIds.length; mi++) if (!sessionById(memberIds[mi])) return false;
@@ -497,3 +496,5 @@ export function initSplitView() {
497
496
  initSplitViewPresentations(host);
498
497
  renderSplit(store.get('splitPanes'));
499
498
  }
499
+
500
+ export function repaintLegacySplit() { if (store.get('splitPanes')) renderSplit(store.get('splitPanes')); }
@@ -14,7 +14,7 @@ import { enhanceClayLogLinks } from './clay-log-links.js';
14
14
  import { closeHumanNote } from './sticky-notes-actions.js';
15
15
  import { send, getNoteUiState } from './sticky-notes-shared.js';
16
16
  import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
17
- import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
17
+ import { handlesWorkbenchKey, claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
18
18
 
19
19
  var panel = null;
20
20
  var gridEl = null;
@@ -427,7 +427,7 @@ export function isNotesBrowserOpen() {
427
427
  export function initNotesBrowser() {
428
428
  registerRightWorkbench("notes-browser", closeNotesBrowser);
429
429
  document.addEventListener("keydown", function (e) {
430
- if (e.key !== "Escape") return;
430
+ if (e.key !== "Escape" || !handlesWorkbenchKey("notes-browser", e)) return;
431
431
  if (deleteDialog && !deleteDialog.classList.contains("hidden")) return;
432
432
  if (!store.get('notesBrowserOpen')) return;
433
433
  closeNotesBrowser();
@@ -104,11 +104,17 @@ function recomputeFlightGeometry() {
104
104
  function syncFlightControls() {
105
105
  var buttons = document.querySelectorAll("#sticky-notes-toggle-btn");
106
106
  var hidden = !!store.get("notesTemporarilyHidden");
107
- var label = hidden ? "Show sticky notes" : "Hide sticky notes";
107
+ var count = openCount();
108
+ var label = hidden ? "Show sticky notes (" + count + ")" : "Hide sticky notes";
108
109
  for (var i = 0; i < buttons.length; i++) {
109
110
  buttons[i].setAttribute("aria-label", label);
110
111
  buttons[i].setAttribute("aria-pressed", hidden ? "true" : "false");
111
112
  buttons[i].title = label;
113
+ var badge = buttons[i].querySelector('.sticky-notes-count');
114
+ if (badge) {
115
+ badge.textContent = String(count);
116
+ badge.classList.toggle('hidden', !hidden || count === 0);
117
+ }
112
118
  }
113
119
  refreshIcons();
114
120
  }
@@ -246,15 +252,7 @@ export function createNote() {
246
252
 
247
253
  function updateBadge() {
248
254
  syncToggleAvailability();
249
- var badge = document.querySelector(".sticky-notes-count");
250
- if (!badge) return;
251
- var count = openCount();
252
- if (count > 0) {
253
- badge.textContent = count;
254
- badge.classList.remove("hidden");
255
- } else {
256
- badge.classList.add("hidden");
257
- }
255
+ syncFlightControls();
258
256
  }
259
257
 
260
258
  // --- WS message handlers ---
@@ -1,3 +1,5 @@
1
+ import { recordToolExpansion } from './view-presentation-state.js';
2
+ import { workspaceChanged } from './view-workspace-state.js';
1
3
  import { store } from './store.js';
2
4
  import { iconHtml, refreshIcons } from './icons.js';
3
5
  import { closeSidebar } from './sidebar.js';
@@ -145,13 +147,8 @@ export function initTerminal(_ctx) {
145
147
 
146
148
  // Fullscreen toggle
147
149
  document.getElementById("terminal-fullscreen").addEventListener("click", function () {
148
- var isFs = ctx.terminalContainerEl.classList.toggle("panel-fullscreen");
149
- var icon = this.querySelector("[data-lucide]");
150
- if (icon) {
151
- icon.setAttribute("data-lucide", isFs ? "minimize-2" : "maximize-2");
152
- refreshIcons();
153
- }
154
- fitTerminal();
150
+ setTerminalExpanded(!ctx.terminalContainerEl.classList.contains('panel-fullscreen'));
151
+ recordToolExpansion('terminal', ctx.terminalContainerEl.classList.contains('panel-fullscreen'));
155
152
  });
156
153
 
157
154
  // Header toggle button
@@ -224,6 +221,7 @@ export function openTerminal() {
224
221
 
225
222
  // --- Close terminal panel (hide, detach, but keep PTYs alive) ---
226
223
  export function closeTerminal() {
224
+ recordToolExpansion('terminal', false);
227
225
  releaseRightWorkbench("terminal");
228
226
  var container = ctx.terminalContainerEl;
229
227
  container.classList.remove("panel-fullscreen");
@@ -279,7 +277,7 @@ function closeTab(termId) {
279
277
  }
280
278
 
281
279
  // --- Activate a tab (show xterm, attach) ---
282
- function activateTab(termId) {
280
+ function activateTab(termId, savedBinding) {
283
281
  var tab = tabs.get(termId);
284
282
  if (!tab) return;
285
283
 
@@ -294,6 +292,10 @@ function activateTab(termId) {
294
292
  }
295
293
 
296
294
  activeTabId = termId;
295
+ var binding = (store.get("clayControlTerminalViews") || []).find(function (item) { return item.id === termId; });
296
+ if (binding) store.set({ savedViewTerminalResource: { id: binding.id, identity: binding.identity, runtimeId: binding.runtimeId } });
297
+ var unavailable = document.getElementById("terminal-saved-unavailable"); if (unavailable) unavailable.remove();
298
+ workspaceChanged();
297
299
 
298
300
  // Lazy-create xterm instance
299
301
  if (!tab.xterm) {
@@ -305,7 +307,7 @@ function activateTab(termId) {
305
307
 
306
308
  // Attach to server
307
309
  if (ctx.ws && ctx.connected) {
308
- ctx.ws.send(JSON.stringify({ type: "term_attach", id: termId }));
310
+ ctx.ws.send(JSON.stringify({ type: "term_attach", id: termId, savedBinding: savedBinding }));
309
311
  }
310
312
 
311
313
  // Fit and focus
@@ -557,11 +559,13 @@ function renderTabBar() {
557
559
 
558
560
  var moreBtn = document.createElement("button");
559
561
  moreBtn.className = "terminal-tab-more";
560
- moreBtn.innerHTML = '<i data-lucide="ellipsis" style="width:12px;height:12px"></i>';
562
+ moreBtn.innerHTML = iconHtml('x');
563
+ moreBtn.setAttribute('aria-label', 'Close terminal tab ' + t.title);
561
564
  moreBtn.addEventListener("click", function (e) {
562
565
  e.stopPropagation();
563
- showTabContextMenu(e, t, label);
566
+ closeTab(t.id);
564
567
  });
568
+ el.addEventListener('contextmenu', function (event) { event.preventDefault(); showTabContextMenu(event, t, label); });
565
569
  el.appendChild(moreBtn);
566
570
 
567
571
  el.addEventListener("click", function () {
@@ -699,7 +703,7 @@ function updateTerminalBadge() {
699
703
  // --- Handle server messages ---
700
704
 
701
705
  export function handleTermList(msg) {
702
- store.set({ clayControlTerminalViews: (msg.terminals || []).map(function (item) { return { id: item.id, identity: item.controlIdentity, revision: item.controlRevision, runtimeId: store.get('controlRuntimeId') }; }) });
706
+ store.set({ clayControlTerminalViews: (msg.terminals || []).map(function (item) { return { id: item.id, identity: item.controlIdentity, revision: item.controlRevision, exited: item.exited, runtimeId: store.get('controlRuntimeId') }; }) });
703
707
  // TUI session terminals are owned by session-tui-view.js and must not
704
708
  // appear in the bottom terminal panel. Filter them out before any tab
705
709
  // bookkeeping so the panel never tries to render or auto-attach them.
@@ -744,6 +748,7 @@ export function handleTermList(msg) {
744
748
  }
745
749
 
746
750
  renderTabBar();
751
+ store.set({ savedViewTerminalListReady: true });
747
752
 
748
753
  // If panel is open and we have tabs, re-attach
749
754
  if (isOpen && tabs.size > 0) {
@@ -825,6 +830,7 @@ export function handleTermClosed(msg) {
825
830
 
826
831
  // --- Reset on reconnect ---
827
832
  export function resetTerminals() {
833
+ store.set({ savedViewTerminalListReady: false, savedViewTerminalResource: null });
828
834
  // Dispose all xterm instances (server state survives, client re-syncs via term_list)
829
835
  for (var tab of tabs.values()) {
830
836
  disposeTab(tab);
@@ -842,7 +848,8 @@ export function sendTerminalCommand(command) {
842
848
  }
843
849
  }
844
850
 
845
- export function setTerminalTheme(xtermTheme) {
851
+ export function setTerminalTheme() {
852
+ var xtermTheme = getTerminalTheme();
846
853
  for (var tab of tabs.values()) {
847
854
  if (tab.xterm) {
848
855
  tab.xterm.options.theme = xtermTheme;
@@ -850,6 +857,7 @@ export function setTerminalTheme(xtermTheme) {
850
857
  }
851
858
  }
852
859
 
860
+
853
861
  // Live font update: applies family/size to every open shell terminal
854
862
  // and refits each (cell dimensions change with font size, so cols/rows
855
863
  // shift too). Hooked via onTerminalFontChange below.
@@ -978,3 +986,27 @@ function showTermCtxMenu(e, tab) {
978
986
  document.addEventListener("click", closeTermCtxMenu, { once: true });
979
987
  }, 0);
980
988
  }
989
+
990
+ // Restoration must never enter openTerminal's create-new branch.
991
+ export function restoreExistingTerminal(resource) {
992
+ if (!store.get('connected') || !store.get('controlRuntimeId')) return 'pending';
993
+ if (!store.get('savedViewTerminalListReady')) return 'pending';
994
+ var live = (store.get('clayControlTerminalViews') || []).find(function (item) { return item.id === resource.id && item.identity === resource.identity && item.runtimeId === resource.runtimeId && !item.exited; });
995
+ claimRightWorkbench('terminal');
996
+ var container = document.getElementById('terminal-container'); container.classList.remove('hidden');
997
+ if (live && tabs.has(resource.id)) { isOpen = true; activateTab(resource.id, resource); return 'restored'; }
998
+ isOpen = false;
999
+ tabs.forEach(function (tab) { if (tab.bodyEl) tab.bodyEl.style.display = 'none'; });
1000
+ var notice = document.getElementById('terminal-saved-unavailable');
1001
+ if (!notice) { notice = document.createElement('p'); notice.id = 'terminal-saved-unavailable'; notice.className = 'saved-view-unavailable'; notice.setAttribute('role', 'status'); document.getElementById('terminal-body').appendChild(notice); }
1002
+ notice.textContent = 'This saved terminal is unavailable. Close this pane and open Terminal to choose an available shell.';
1003
+ return 'unavailable';
1004
+ }
1005
+
1006
+ export function setTerminalExpanded(enabled) {
1007
+ var panel = document.getElementById('terminal-container'); if (!panel) return;
1008
+ panel.classList.toggle('panel-fullscreen', !!enabled);
1009
+ var icon = panel.querySelector('#terminal-fullscreen [data-lucide]');
1010
+ if (icon) { icon.setAttribute('data-lucide', enabled ? 'minimize-2' : 'maximize-2'); refreshIcons(); }
1011
+ if (!panel.classList.contains('hidden')) fitTerminal();
1012
+ }
@@ -327,7 +327,7 @@ export function applyTheme(themeId, fromPicker) {
327
327
  // Terminals deliberately keep Clay Studio Dark for predictable ANSI
328
328
  // contrast, even while the surrounding workspace uses a light theme.
329
329
  var termTheme = getTerminalTheme();
330
- try { setTerminalTheme(termTheme); } catch (e) {}
330
+ try { setTerminalTheme(); } catch (e) {}
331
331
  try { setTuiSessionTheme(termTheme); } catch (e) {}
332
332
  try { setTuiAttentionTheme(termTheme); } catch (e) {}
333
333
 
@@ -0,0 +1,82 @@
1
+ import { WORKBENCH_PANELS } from './workbench-panels.js';
2
+ import { recordToolExpansion } from './view-presentation-state.js';
3
+ import { store } from './store.js';
4
+ import { iconHtml, refreshIcons } from './icons.js';
5
+ import { TOOL_PANES } from './tool-pane-tree.js';
6
+
7
+ function titleFor(panel, name) {
8
+ if (name === 'terminal') {
9
+ var state = store.snap(), project = (state.projectsHubList || []).find(function (item) { return item && item.slug === state.currentSlug; });
10
+ var projectName = project && (project.title || project.project || project.name) || state.projectName || state.currentSlug;
11
+ return projectName ? projectName + ' · Terminal' : 'Terminal';
12
+ }
13
+ if (name === 'file-viewer') { var tab = panel.querySelector('.file-viewer-tab.active .file-viewer-tab-label'); return tab && tab.textContent || 'Files'; }
14
+ var browser = store.get('sharedBrowser');
15
+ try { return browser && browser.url ? new URL(browser.url).hostname : 'Browser'; } catch (error) { return 'Browser'; }
16
+ }
17
+ export function renderToolPaneChrome(panel, name) {
18
+ if (panel.dataset.fileInstance) return;
19
+ if (WORKBENCH_PANELS[name]) { renderWorkbenchChrome(panel, name); return; }
20
+ var definition = TOOL_PANES[name];
21
+ if (!definition) return;
22
+ var header = panel.querySelector(':scope > .tool-pane-header');
23
+ if (!header) {
24
+ var original = panel.querySelector(name === 'terminal' ? '.terminal-header' : name === 'browser' ? '.shared-browser-header' : '.file-viewer-tabbar');
25
+ if (!original) return;
26
+ header = document.createElement('header'); header.className = 'tool-pane-header split-pane-header'; header.dataset.toolPane = name;
27
+ var kind = document.createElement('span'); kind.className = 'tool-pane-kind' + (name === 'terminal' ? '' : ' split-view-runtime');
28
+ kind.innerHTML = iconHtml(definition.icon) + (name === 'terminal' ? '' : '<span class="split-view-model">' + definition.label + '</span>');
29
+ var title = document.createElement('span'); title.className = 'tool-pane-title split-pane-title';
30
+ var space = document.createElement('span'); space.className = 'split-pane-drag-space'; space.title = 'Drag to rearrange panes'; space.setAttribute('aria-hidden', 'true');
31
+ var actions = original.querySelector('.terminal-header-actions, .shared-browser-window, .file-viewer-window-actions');
32
+ header.append(kind, title, space); if (actions) header.appendChild(actions);
33
+ panel.insertBefore(header, panel.firstChild);
34
+ if (name === 'browser') { original.hidden = true; var close = header.querySelector('[data-action="hide"]'); if (close) { close.title = 'Close browser pane'; close.setAttribute('aria-label', 'Close browser pane'); } }
35
+ else original.classList.add('tool-pane-tab-row');
36
+ var observer = new MutationObserver(function () { update(); });
37
+ observer.observe(panel, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ['aria-selected'] });
38
+ panel.addEventListener('clay-tool-pane-dispose', function () { observer.disconnect(); }, { once: true });
39
+ refreshIcons();
40
+ }
41
+ function update() {
42
+ var label = titleFor(panel, name), title = header.querySelector('.tool-pane-title');
43
+ if (title.textContent !== label) { title.textContent = label; title.title = label; }
44
+ }
45
+ update();
46
+ }
47
+
48
+ store.subscribe(function (state, previous) {
49
+ if (state.projectName === previous.projectName && state.projectsHubList === previous.projectsHubList && state.currentSlug === previous.currentSlug) return;
50
+ var panel = document.getElementById('terminal-container');
51
+ if (panel && panel.querySelector(':scope > .tool-pane-header')) renderToolPaneChrome(panel, 'terminal');
52
+ });
53
+
54
+ function renderWorkbenchChrome(panel, name) {
55
+ panel.classList.add('workbench-pane-shell');
56
+ var header = panel.querySelector(':scope > header');
57
+ if (!header || header.dataset.toolPane === name) return;
58
+ header.dataset.toolPane = name;
59
+ header.classList.add('tool-pane-header', 'split-pane-header');
60
+ var actions = header.querySelector('[class*="window-actions"], :scope > div:last-child');
61
+ if (!actions) {
62
+ actions = document.createElement('div');
63
+ Array.from(header.querySelectorAll(':scope > button')).forEach(function (button) {
64
+ var label = button.getAttribute('aria-label') || '';
65
+ if (/^(Widen|Close)|fullscreen/i.test(label)) actions.appendChild(button);
66
+ });
67
+ header.appendChild(actions);
68
+ }
69
+ actions.classList.add('workbench-pane-actions');
70
+ var title = header.querySelector('strong, [class$="-title"]');
71
+ if (title) title.classList.add('tool-pane-title');
72
+ var space = document.createElement('span'); space.className = 'split-pane-drag-space';
73
+ space.title = 'Drag to rearrange panes'; space.setAttribute('aria-hidden', 'true');
74
+ header.insertBefore(space, actions);
75
+ var expanded = panel.classList.contains('panel-fullscreen');
76
+ var observer = new MutationObserver(function () {
77
+ var next = panel.classList.contains('panel-fullscreen');
78
+ if (next !== expanded) { expanded = next; recordToolExpansion(name, next); }
79
+ });
80
+ observer.observe(panel, { attributes: true, attributeFilter: ['class'] });
81
+ panel.addEventListener('clay-tool-pane-dispose', function () { observer.disconnect(); }, { once: true });
82
+ }
@@ -0,0 +1,134 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { livePaneBinding, samePaneBinding } from './pane-binding.js';
4
+ import { insertWindow } from './tool-pane-tree.js';
5
+ import { resize } from './split-view-layout-client.js';
6
+ import { repaintToolPanes, saveToolPaneTree, toolPaneContext } from './tool-pane-layout.js';
7
+
8
+ function receiptCurrent(drag) {
9
+ var current = store.get('toolPaneWindowGeometry');
10
+ return current && current.key === toolPaneContext() && store.get('connected') && current.key === drag.receipt.key && current.socket === getWs() && current.socket === drag.receipt.socket &&
11
+ current.scope === drag.receipt.scope && current.scope === JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('currentProjectOwnerId'), store.get('projectSplitViewSocketGeneration')]) && current.revision === drag.receipt.revision &&
12
+ JSON.stringify(current.windows.map(function (entry) { return entry.id; }).sort()) === JSON.stringify(drag.windows.map(function (entry) { return entry.id; }).sort()) && JSON.stringify(current.base) === JSON.stringify(drag.receipt.base) &&
13
+ current.host.clientWidth === drag.width && current.host.clientHeight === drag.height &&
14
+ drag.windows.every(function (entry) {
15
+ if (!entry.element.isConnected || entry.element.classList.contains('hidden')) return false;
16
+ if (!entry.frame) return entry.element.dataset.toolWindowId === entry.id;
17
+ var live = entry.frame.clayPane && entry.frame.clayPane.identity();
18
+ return entry.frame.contentDocument === entry.document && entry.frame.contentWindow === entry.window &&
19
+ live && samePaneBinding(live.binding, entry.binding) && livePaneBinding(entry.frame);
20
+ });
21
+ }
22
+ function capture(event, source, divider) {
23
+ var receipt = store.get('toolPaneWindowGeometry'), preference = store.get('workbenchLayoutPreference') || {};
24
+ if (!receipt || (!store.get('projectSplitViewActiveId') && (!preference.ready || preference.loading || preference.saving)) || store.get('projectSplitViewDrag') || store.get('projectSplitViewPending')) return null;
25
+ var drag = { pointerId: event.pointerId, receipt: receipt, source: source, divider: divider, startX: event.clientX, startY: event.clientY,
26
+ width: receipt.host.clientWidth, height: receipt.host.clientHeight, tree: receipt.tree, moved: false, windows: receipt.windows.map(function (item) {
27
+ var live = item.frame && item.frame.clayPane && item.frame.clayPane.identity();
28
+ return Object.assign({}, item, { rect: item.element.getBoundingClientRect(), document: item.frame && item.frame.contentDocument,
29
+ window: item.frame && item.frame.contentWindow, binding: live && live.binding, pointerEvents: item.frame && item.frame.style.pointerEvents });
30
+ }) };
31
+ return receiptCurrent(drag) ? drag : null;
32
+ }
33
+ function begin(drag) {
34
+ drag.moved = true;
35
+ drag.windows.forEach(function (item) { if (item.frame) item.frame.style.pointerEvents = 'none'; });
36
+ if (drag.source) drag.source.element.classList.add('tool-pane-drag-source');
37
+ document.body.classList.add('tool-pane-dragging'); drag.receipt.host.classList.add('split-view-drag-preview');
38
+ try { document.documentElement.setPointerCapture(drag.pointerId); } catch (error) {}
39
+ }
40
+ function pointerDown(event) {
41
+ if (event.button !== 0 || event.isPrimary === false || store.get('toolPaneDrag')) return;
42
+ var header = event.target.closest && event.target.closest('.tool-pane-window > .tool-pane-header, #split-host > .split-pane > .split-pane-header');
43
+ if (!header || event.target.closest('button, input, a, .tool-pane-title, .tool-pane-kind, .split-pane-title, .split-view-runtime, .split-worker-runtime')) return;
44
+ var receipt = store.get('toolPaneWindowGeometry');
45
+ if (!receipt || !receipt.windows.some(function (item) { return !!item.tool; })) return;
46
+ var source = receipt.windows.find(function (item) { return item.element === header.parentNode; });
47
+ var drag = source && capture(event, source);
48
+ if (!drag) return;
49
+ store.set({ toolPaneDrag: drag }); event.preventDefault(); event.stopImmediatePropagation();
50
+ }
51
+ function preview(drag, tree) {
52
+ drag.tree = tree;
53
+ store.set({ toolPaneLayoutPreview: { key: drag.receipt.key, base: drag.receipt.base, tree: tree, socket: drag.receipt.socket } });
54
+ repaintToolPanes();
55
+ }
56
+ function move(event) {
57
+ var drag = store.get('toolPaneDrag');
58
+ if (!drag || drag.pointerId !== event.pointerId) return;
59
+ if (!receiptCurrent(drag)) { finish(false); return; }
60
+ if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 6) return;
61
+ if (!drag.moved) begin(drag);
62
+ event.preventDefault();
63
+ if (drag.divider) {
64
+ var entry = drag.divider, horizontal = entry.axis === 'horizontal';
65
+ var delta = horizontal ? event.clientX - drag.startX : event.clientY - drag.startY;
66
+ var next = resize(drag.receipt.tree, entry.path, Math.max(.05, Math.min(.95, entry.ratio + delta / Math.max(1, entry.available))));
67
+ if (next) preview(drag, next);
68
+ return;
69
+ }
70
+ var target = drag.windows.find(function (entry) { var r = entry.rect; return event.clientX >= r.left && event.clientX <= r.right && event.clientY >= r.top && event.clientY <= r.bottom; });
71
+ var nextTree = drag.receipt.tree;
72
+ if (target && target.id !== drag.source.id) {
73
+ var r = target.rect, x = (event.clientX - r.left) / r.width, y = (event.clientY - r.top) / r.height;
74
+ var edge = Math.min(x, 1 - x, y, 1 - y), side = 'center';
75
+ if (edge < .25) side = edge === x ? 'left' : edge === 1 - x ? 'right' : edge === y ? 'top' : 'bottom';
76
+ nextTree = insertWindow(drag.receipt.tree, drag.source.id, target.id, side);
77
+ }
78
+ preview(drag, nextTree);
79
+ var ghost = document.getElementById('tool-pane-drag-ghost');
80
+ if (!ghost) { ghost = document.createElement('div'); ghost.id = 'tool-pane-drag-ghost'; ghost.className = 'split-view-drag-ghost'; document.body.appendChild(ghost); }
81
+ ghost.textContent = drag.source.element.querySelector('.tool-pane-title, .split-pane-title').textContent;
82
+ ghost.style.left = event.clientX + 12 + 'px'; ghost.style.top = event.clientY + 12 + 'px'; ghost.hidden = false;
83
+ }
84
+ async function finish(commit) {
85
+ var drag = store.get('toolPaneDrag');
86
+ if (!drag) return;
87
+ var allowed = commit && drag.moved && receiptCurrent(drag) && JSON.stringify(drag.tree) !== JSON.stringify(drag.receipt.tree);
88
+ store.set({ toolPaneDrag: null });
89
+ drag.windows.forEach(function (entry) { if (entry.frame && entry.frame.isConnected) entry.frame.style.pointerEvents = entry.pointerEvents; });
90
+ if (drag.source) drag.source.element.classList.remove('tool-pane-drag-source');
91
+ document.body.classList.remove('tool-pane-dragging');
92
+ var ghost = document.getElementById('tool-pane-drag-ghost'); if (ghost) ghost.hidden = true;
93
+ try { if (document.documentElement.hasPointerCapture(drag.pointerId)) document.documentElement.releasePointerCapture(drag.pointerId); } catch (error) {}
94
+ var saved = allowed ? await saveToolPaneTree(drag.receipt, drag.tree) : false;
95
+ if (!store.get('toolPaneDrag')) { store.set({ toolPaneLayoutPreview: null }); repaintToolPanes(); }
96
+ setTimeout(function () { if (!store.get('toolPaneDrag')) drag.receipt.host.classList.remove('split-view-drag-preview'); }, 320);
97
+ return saved;
98
+ }
99
+ function up(event) {
100
+ var drag = store.get('toolPaneDrag');
101
+ if (!drag || drag.pointerId !== event.pointerId) return;
102
+ if (drag.moved) move(event);
103
+ finish(true);
104
+ }
105
+ export function bindToolPaneDivider(divider, entry, tree) {
106
+ divider.addEventListener('pointerdown', function (event) {
107
+ if (event.button !== 0 || store.get('toolPaneDrag')) return;
108
+ var drag = capture(event, null, entry);
109
+ if (!drag) return;
110
+ store.set({ toolPaneDrag: drag }); begin(drag); event.preventDefault(); event.stopImmediatePropagation();
111
+ });
112
+ divider.addEventListener('keydown', function (event) {
113
+ var direction = ['ArrowLeft', 'ArrowUp'].indexOf(event.key) >= 0 ? -1 : ['ArrowRight', 'ArrowDown'].indexOf(event.key) >= 0 ? 1 : 0;
114
+ if (!direction) return;
115
+ var receipt = store.get('toolPaneWindowGeometry'), next = resize(tree, entry.path, Math.max(.05, Math.min(.95, entry.ratio + direction * .03)));
116
+ if (receipt && next) { event.preventDefault(); saveToolPaneTree(receipt, next); }
117
+ });
118
+ }
119
+ export function initToolPaneDrag() {
120
+ if (store.get('toolPaneDragInitialized')) return;
121
+ store.set({ toolPaneDragInitialized: true });
122
+ document.addEventListener('pointerdown', pointerDown, true);
123
+ document.addEventListener('pointermove', move, true); document.addEventListener('pointerup', up, true);
124
+ document.addEventListener('pointercancel', function () { finish(false); }, true);
125
+ document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && store.get('toolPaneDrag')) { event.preventDefault(); finish(false); } }, true);
126
+ window.addEventListener('blur', function () { finish(false); });
127
+ }
128
+
129
+ store.subscribe(function (state, previous) {
130
+ if (state.toolPaneDrag && (state.myUserId !== previous.myUserId || state.currentSlug !== previous.currentSlug ||
131
+ state.currentProjectOwnerId !== previous.currentProjectOwnerId || state.splitPanes !== previous.splitPanes ||
132
+ state.connected !== previous.connected || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId ||
133
+ state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration)) finish(false);
134
+ });
@@ -0,0 +1,95 @@
1
+ import { boundLegacyCoordinates } from './view-legacy-coordinates.js';
2
+ import { pairDisplayTree, pairBase } from './view-pair-presentation.js';
3
+ import { activeSavedView, displayTree, fromDisplayTree, copy, workspaceKey } from './view-workspace-state.js';
4
+ import { persistWorkspace } from './view-workspace-controller.js';
5
+ import { store } from './store.js';
6
+ import { getWs } from './ws-ref.js';
7
+ import { TOOL_PANES, toolWindowId, workspaceWindowTree } from './tool-pane-tree.js';
8
+ import { renderPresentationSplitView } from './split-view-presentations.js';
9
+ import { ensureWorkbenchLayoutPreference } from './workbench-layout-preference.js';
10
+ import { showToast } from './utils.js';
11
+ import { initToolPaneDrag, bindToolPaneDivider } from './tool-pane-drag.js';
12
+
13
+ export function toolPaneContext() {
14
+ var state = store.snap(), view = (state.projectSplitViews || []).find(function (item) { return item.id === state.projectSplitViewActiveId; });
15
+ var split = state.splitPanes;
16
+ var origin = view ? view.id : split && split.panes ? 'legacy-' + (split.groupId || split.panes.map(function (pane) { return pane.sessionId; }).join('-')) : state.projectSplitViewSingletonOrigin;
17
+ return origin && state.currentSlug && JSON.stringify([state.currentSlug, state.currentProjectOwnerId || null, origin]);
18
+ }
19
+ export function dockedToolPanes() {
20
+ if (window.innerWidth < 1024 || store.get('paneMode')) return [];
21
+ return (store.get('rightWorkbenchDockedOwners') || []).filter(function (name) {
22
+ var definition = TOOL_PANES[name], panel = definition && document.getElementById(definition.id);
23
+ return panel && !panel.classList.contains('hidden') && !panel.classList.contains('panel-fullscreen');
24
+ });
25
+ }
26
+ export function toolPaneLayoutTree(view, base) {
27
+ initToolPaneDrag();
28
+ var owners = dockedToolPanes(), key = toolPaneContext(), preference = store.get('workbenchLayoutPreference') || {};
29
+ var draft = store.get('toolPaneLayoutPreview'), saved = preference.layouts && preference.layouts[key];
30
+ if (view && view.workspace) {
31
+ var legacy = store.get('savedWorkspaceLegacyLayout');
32
+ saved = legacy && legacy.key === workspaceKey(view) ? boundLegacyCoordinates(legacy.record, view.workspace) : { base: view.workspace.linkedPair ? pairBase(view.workspace.linkedPair, view.tree) : view.tree, tree: pairDisplayTree(view.workspace) };
33
+ }
34
+ if (draft && draft.key === key && JSON.stringify(draft.base) === JSON.stringify(base) && draft.socket === getWs()) return draft.tree;
35
+ return owners.length || saved ? workspaceWindowTree(base, saved, owners) : base;
36
+ }
37
+ export function paintToolPaneWindows(host, view, base, tree, geometry, inset) {
38
+ var owners = dockedToolPanes(), key = toolPaneContext(), main = document.getElementById('main-panels');
39
+ if (!main) return;
40
+ main.classList.toggle('view-session-focused', !!store.get('projectSplitViewFocusedOrigin'));
41
+ var hostRect = host.getBoundingClientRect(), mainRect = main.getBoundingClientRect();
42
+ var windows = Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)')).map(function (pane) {
43
+ var frame = pane.querySelector('.split-pane-frame'); return { id: frame && frame.dataset.sessionOriginId, element: pane, frame: frame };
44
+ });
45
+ Object.keys(TOOL_PANES).forEach(function (name) {
46
+ var panel = document.getElementById(TOOL_PANES[name].id), rect = geometry.panes[toolWindowId(name)];
47
+ if (!panel) return;
48
+ if (!panel.dataset.toolPaneFullscreenObserved) {
49
+ panel.dataset.toolPaneFullscreenObserved = 'true';
50
+ var observer = new MutationObserver(function (records) {
51
+ if (records.some(function (record) { return (String(record.oldValue || '').split(/\s+/).indexOf('panel-fullscreen') >= 0) !== panel.classList.contains('panel-fullscreen'); })) requestAnimationFrame(repaintToolPanes);
52
+ });
53
+ observer.observe(panel, { attributes: true, attributeOldValue: true, attributeFilter: ['class'] });
54
+ panel.addEventListener('clay-tool-pane-dispose', function () { observer.disconnect(); }, { once: true });
55
+ }
56
+ var mounted = owners.indexOf(name) >= 0 && !!rect;
57
+ var wasMounted = panel.classList.contains('tool-pane-window');
58
+ panel.classList.toggle('tool-pane-window', mounted);
59
+ if (!mounted) {
60
+ if (wasMounted) ['left', 'top', 'width', 'height', 'right', 'maxHeight'].forEach(function (field) { panel.style[field] = ''; });
61
+ delete panel.dataset.toolWindowId; return;
62
+ }
63
+ panel.dataset.toolWindowId = toolWindowId(name);
64
+ panel.style.left = hostRect.left - mainRect.left + inset.left + rect.x + 'px';
65
+ panel.style.top = hostRect.top - mainRect.top + inset.top + rect.y + 'px';
66
+ panel.style.width = rect.width + 'px'; panel.style.height = rect.height + 'px'; panel.style.right = 'auto'; panel.style.maxHeight = 'none';
67
+ windows.push({ id: toolWindowId(name), element: panel, tool: name });
68
+ });
69
+ main.classList.toggle('has-tool-pane-windows', owners.length > 0);
70
+ if (Math.abs(host.clientWidth - hostRect.width) > 1) requestAnimationFrame(repaintToolPanes);
71
+ store.set({ toolPaneWindowGeometry: { key: key, base: base, tree: tree, view: view, windows: windows,
72
+ host: host, socket: getWs(), revision: view && view.workspace ? view.revision : (store.get('workbenchLayoutPreference') || {}).revision,
73
+ scope: JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('currentProjectOwnerId'), store.get('projectSplitViewSocketGeneration')]) } });
74
+ }
75
+ export function bindWorkspaceDivider(divider, entry, tree) { bindToolPaneDivider(divider, entry, tree); }
76
+ export function repaintToolPanes() { renderPresentationSplitView(); }
77
+ export async function saveToolPaneTree(receipt, tree) {
78
+ var live = store.get('toolPaneWindowGeometry');
79
+ if (!live || live.key !== toolPaneContext() || live.key !== receipt.key || live.socket !== receipt.socket || live.scope !== receipt.scope || live.revision !== receipt.revision || !store.get('connected')) return false;
80
+ var view = activeSavedView();
81
+ if (view) { var value = copy(view.workspace); value.tree = fromDisplayTree(value, tree); if (value.linkedPair) value.linkedPair.tree = copy(tree); return persistWorkspace(value); }
82
+ showToast('Wait for the saved View before changing pane positions.', 'warn');
83
+ return false;
84
+ }
85
+ store.subscribe(function (state, previous) {
86
+ if (state.paneMode) return;
87
+ var changed = state.rightWorkbenchDockedOwners !== previous.rightWorkbenchDockedOwners || state.workbenchLayoutPreference !== previous.workbenchLayoutPreference ||
88
+ state.toolPaneLayoutPreview !== previous.toolPaneLayoutPreview;
89
+ if (!changed) return;
90
+ if (!state.toolPaneRepaintQueued) {
91
+ store.set({ toolPaneRepaintQueued: true });
92
+ requestAnimationFrame(function () { store.set({ toolPaneRepaintQueued: false }); renderPresentationSplitView(); });
93
+ }
94
+ if ((state.rightWorkbenchDockedOwners || []).some(function (name) { return !!TOOL_PANES[name]; }) && !(state.workbenchLayoutPreference || {}).ready && !(state.workbenchLayoutPreference || {}).loading && !(state.workbenchLayoutPreference || {}).saving && !(state.workbenchLayoutPreference || {}).error) ensureWorkbenchLayoutPreference();
95
+ });