clay-server 5.3.0-beta.5 → 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 (129) hide show
  1. package/lib/clay-product-control-tools.js +2 -2
  2. package/lib/clay-product-control.js +1 -1
  3. package/lib/clay-screen-controls.js +1 -0
  4. package/lib/clay-split-view-awareness.js +5 -2
  5. package/lib/clay-split-view-controls.js +14 -7
  6. package/lib/clay-split-view-input.js +5 -4
  7. package/lib/clay-split-view-render-proof.js +14 -4
  8. package/lib/clay-view-workspace-input.js +71 -0
  9. package/lib/project-file-history.js +11 -9
  10. package/lib/project-file-watch.js +26 -7
  11. package/lib/project-filesystem.js +5 -4
  12. package/lib/project-shared-browser.js +7 -3
  13. package/lib/project-split-view-document.js +16 -2
  14. package/lib/project-split-views.js +15 -294
  15. package/lib/project-user-message.js +4 -3
  16. package/lib/project-vendor-login.js +2 -2
  17. package/lib/project-view-input.js +11 -0
  18. package/lib/project-view-pair.js +26 -0
  19. package/lib/project-view-store.js +346 -0
  20. package/lib/project-view-terminal-binding.js +16 -0
  21. package/lib/project-view-workspace.js +126 -0
  22. package/lib/project.js +1 -0
  23. package/lib/public/app.js +2 -0
  24. package/lib/public/css/issues.css +3 -0
  25. package/lib/public/css/pane.css +5 -1
  26. package/lib/public/css/right-workbench.css +0 -1
  27. package/lib/public/css/scheduled-tasks.css +7 -0
  28. package/lib/public/css/split-view-layout.css +37 -3
  29. package/lib/public/css/tool-pane.css +127 -0
  30. package/lib/public/index.html +1 -1
  31. package/lib/public/modules/clay-file-links.js +2 -1
  32. package/lib/public/modules/clay-split-view-screen.js +6 -3
  33. package/lib/public/modules/clay-tool-pane-screen.js +35 -0
  34. package/lib/public/modules/file-history-snapshot.js +12 -0
  35. package/lib/public/modules/file-pane-entrypoints.js +55 -0
  36. package/lib/public/modules/file-pane-history.js +78 -0
  37. package/lib/public/modules/file-pane-instances.js +140 -0
  38. package/lib/public/modules/file-pane-links.js +41 -0
  39. package/lib/public/modules/file-pane-render.js +48 -0
  40. package/lib/public/modules/file-pane-review.js +26 -0
  41. package/lib/public/modules/file-pane-toolbar.js +54 -0
  42. package/lib/public/modules/file-viewer-source.js +70 -0
  43. package/lib/public/modules/filebrowser.js +43 -82
  44. package/lib/public/modules/git-panel.js +5 -0
  45. package/lib/public/modules/issues.js +3 -4
  46. package/lib/public/modules/linear-panel.js +2 -2
  47. package/lib/public/modules/mate-knowledge-workbench.js +10 -6
  48. package/lib/public/modules/notifications.js +4 -1
  49. package/lib/public/modules/pane-activation.js +2 -0
  50. package/lib/public/modules/pane-binding.js +5 -1
  51. package/lib/public/modules/pane-browser-bridge.js +6 -2
  52. package/lib/public/modules/pane-browser-transport.js +110 -0
  53. package/lib/public/modules/plantuml-preview.js +12 -12
  54. package/lib/public/modules/right-workbench.js +34 -15
  55. package/lib/public/modules/scheduled-tasks.js +7 -6
  56. package/lib/public/modules/shared-browser.js +69 -9
  57. package/lib/public/modules/split-pane-renderer.js +1 -0
  58. package/lib/public/modules/split-view-drag-preview.js +90 -0
  59. package/lib/public/modules/split-view-layout-client.js +1 -1
  60. package/lib/public/modules/split-view-presentation-drag.js +36 -13
  61. package/lib/public/modules/split-view-presentation-drop.js +21 -18
  62. package/lib/public/modules/split-view-presentation-renderer.js +15 -10
  63. package/lib/public/modules/split-view-presentations.js +25 -33
  64. package/lib/public/modules/split-view.js +9 -2
  65. package/lib/public/modules/sticky-notes-browser.js +2 -2
  66. package/lib/public/modules/terminal.js +46 -19
  67. package/lib/public/modules/theme.js +1 -6
  68. package/lib/public/modules/tool-pane-chrome.js +82 -0
  69. package/lib/public/modules/tool-pane-drag.js +134 -0
  70. package/lib/public/modules/tool-pane-layout.js +95 -0
  71. package/lib/public/modules/tool-pane-legacy.js +60 -0
  72. package/lib/public/modules/tool-pane-tree.js +51 -0
  73. package/lib/public/modules/view-legacy-coordinates.js +16 -0
  74. package/lib/public/modules/view-mutations.js +24 -0
  75. package/lib/public/modules/view-pair-presentation.js +74 -0
  76. package/lib/public/modules/view-pair-validation.js +12 -0
  77. package/lib/public/modules/view-presentation-state.js +37 -0
  78. package/lib/public/modules/view-workspace-controller.js +210 -0
  79. package/lib/public/modules/view-workspace-state.js +103 -0
  80. package/lib/public/modules/view-workspace-validation.js +47 -0
  81. package/lib/public/modules/wireframe-workbench.js +11 -0
  82. package/lib/public/modules/workbench-layout-preference.js +12 -5
  83. package/lib/public/modules/workbench-panels.js +21 -0
  84. package/lib/public/pane-assets/{app-6H5S2Y3Z.js → app-7UZ727NA.js} +1 -1
  85. package/lib/public/pane-assets/chunk-2AIWCI6Y.js +1 -0
  86. package/lib/public/pane-assets/chunk-6MIHIQ2W.js +1 -0
  87. package/lib/public/pane-assets/chunk-6NYCNWVF.js +1 -0
  88. package/lib/public/pane-assets/chunk-6QFLYOVY.js +1 -0
  89. package/lib/public/pane-assets/chunk-6UJ2ALOB.js +1 -0
  90. package/lib/public/pane-assets/chunk-6XK2OATZ.js +1 -0
  91. package/lib/public/pane-assets/chunk-BKIDR2ME.js +1 -0
  92. package/lib/public/pane-assets/chunk-BMHTBRMO.js +1 -0
  93. package/lib/public/pane-assets/chunk-GEFCFZEN.js +1 -0
  94. package/lib/public/pane-assets/chunk-GN2NQEVC.js +2 -0
  95. package/lib/public/pane-assets/chunk-IMLMBPT4.js +166 -0
  96. package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
  97. package/lib/public/pane-assets/chunk-K5S6AILM.js +1 -0
  98. package/lib/public/pane-assets/chunk-LDIZLKB2.js +1 -0
  99. package/lib/public/pane-assets/chunk-OWNNLZ36.js +1 -0
  100. package/lib/public/pane-assets/chunk-P2C3LOWL.js +3 -0
  101. package/lib/public/pane-assets/chunk-PV4PCH2V.js +2 -0
  102. package/lib/public/pane-assets/chunk-R4LMPTKU.js +1 -0
  103. package/lib/public/pane-assets/chunk-R7QVVFUG.js +1 -0
  104. package/lib/public/pane-assets/chunk-TMGSDFJI.js +1 -0
  105. package/lib/public/pane-assets/chunk-VKYBAVWT.js +1 -0
  106. package/lib/public/pane-assets/chunk-VNAV23CM.js +1 -0
  107. package/lib/public/pane-assets/chunk-VWTHIQCE.js +1 -0
  108. package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
  109. package/lib/public/pane-assets/chunk-WM3K5AFH.js +1 -0
  110. package/lib/public/pane-assets/chunk-XOB3O327.js +1 -0
  111. package/lib/public/pane-assets/chunk-XOOUCFYT.js +1 -0
  112. package/lib/public/pane-assets/manifest.json +426 -381
  113. package/lib/public/pane-assets/style-ZM7OOU3M.css +1 -0
  114. package/lib/public/style.css +1 -0
  115. package/lib/server-workbench-layout-settings.js +3 -3
  116. package/lib/split-view-layout.js +11 -7
  117. package/lib/users-workbench-layout-preferences.js +14 -4
  118. package/lib/workbench-window-layout.js +30 -0
  119. package/package.json +1 -1
  120. package/lib/public/pane-assets/chunk-3QV5SDC4.js +0 -1
  121. package/lib/public/pane-assets/chunk-4FM3U3DW.js +0 -1
  122. package/lib/public/pane-assets/chunk-7GZH3FZY.js +0 -1
  123. package/lib/public/pane-assets/chunk-AGJTF5WQ.js +0 -1
  124. package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
  125. package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
  126. package/lib/public/pane-assets/chunk-NPKRTUNG.js +0 -2
  127. package/lib/public/pane-assets/chunk-VGA2Y3D2.js +0 -2
  128. package/lib/public/pane-assets/chunk-WKO3T75E.js +0 -167
  129. package/lib/public/pane-assets/style-TSRDU54Z.css +0 -1
@@ -1,9 +1,11 @@
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';
4
6
  import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
5
7
  import { copyToClipboard } from './utils.js';
6
- import { getTerminalTheme, getDockedTerminalTheme } from './theme.js';
8
+ import { getTerminalTheme } from './theme.js';
7
9
  import { getTerminalFontFamily, getTerminalFontSize, onTerminalFontChange } from './terminal-prefs.js';
8
10
  import { createKeyToolbar } from './terminal-toolbar.js';
9
11
 
@@ -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
@@ -358,7 +360,7 @@ function createXtermForTab(tab) {
358
360
  cursorBlink: true,
359
361
  fontSize: getTerminalFontSize(),
360
362
  fontFamily: getTerminalFontFamily(),
361
- theme: (store.get('rightWorkbenchDockedOwners') || []).indexOf('terminal') !== -1 ? getDockedTerminalTheme() : getTerminalTheme(),
363
+ theme: getTerminalTheme(),
362
364
  });
363
365
 
364
366
  var fitAddon = null;
@@ -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,8 +848,8 @@ export function sendTerminalCommand(command) {
842
848
  }
843
849
  }
844
850
 
845
- export function setTerminalTheme(xtermTheme) {
846
- if ((store.get('rightWorkbenchDockedOwners') || []).indexOf('terminal') !== -1) xtermTheme = getDockedTerminalTheme();
851
+ export function setTerminalTheme() {
852
+ var xtermTheme = getTerminalTheme();
847
853
  for (var tab of tabs.values()) {
848
854
  if (tab.xterm) {
849
855
  tab.xterm.options.theme = xtermTheme;
@@ -851,9 +857,6 @@ export function setTerminalTheme(xtermTheme) {
851
857
  }
852
858
  }
853
859
 
854
- store.subscribe(function (state, previous) {
855
- if ((state.rightWorkbenchDockedOwners || []).includes('terminal') !== (previous.rightWorkbenchDockedOwners || []).includes('terminal')) setTerminalTheme(getTerminalTheme());
856
- });
857
860
 
858
861
  // Live font update: applies family/size to every open shell terminal
859
862
  // and refits each (cell dimensions change with font size, so cols/rows
@@ -983,3 +986,27 @@ function showTermCtxMenu(e, tab) {
983
986
  document.addEventListener("click", closeTermCtxMenu, { once: true });
984
987
  }, 0);
985
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
+ }
@@ -267,11 +267,6 @@ export function getTerminalTheme() {
267
267
  return computeTerminalTheme(getTheme(DEFAULT_DARK_THEME_ID) || defaultDarkFallback);
268
268
  }
269
269
 
270
- export function getDockedTerminalTheme() {
271
- var theme = getCurrentTheme();
272
- return Object.assign({}, computeTerminalTheme(theme), { background: '#' + theme.base00 });
273
- }
274
-
275
270
  export function getMermaidThemeVars() {
276
271
  return computeMermaidVars(getCurrentTheme());
277
272
  }
@@ -332,7 +327,7 @@ export function applyTheme(themeId, fromPicker) {
332
327
  // Terminals deliberately keep Clay Studio Dark for predictable ANSI
333
328
  // contrast, even while the surrounding workspace uses a light theme.
334
329
  var termTheme = getTerminalTheme();
335
- try { setTerminalTheme(termTheme); } catch (e) {}
330
+ try { setTerminalTheme(); } catch (e) {}
336
331
  try { setTuiSessionTheme(termTheme); } catch (e) {}
337
332
  try { setTuiAttentionTheme(termTheme); } catch (e) {}
338
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
+ });
@@ -0,0 +1,60 @@
1
+ import { ensureArrangeTrigger } from './split-view-presentation-renderer.js';
2
+ import { pairBase } from './view-pair-presentation.js';
3
+ import { activeSavedView } from './view-workspace-state.js';
4
+ // Legacy Driver/Worker pairs use the same display-only window geometry.
5
+ import { store } from './store.js';
6
+ import { rectangles } from './split-view-layout-client.js';
7
+ import { toolPaneLayoutTree, paintToolPaneWindows, bindWorkspaceDivider, dockedToolPanes } from './tool-pane-layout.js';
8
+
9
+ export function clearToolPaneWindows() {
10
+ document.querySelectorAll('.tool-pane-window').forEach(function (panel) {
11
+ panel.classList.remove('tool-pane-window'); delete panel.dataset.toolWindowId;
12
+ ['left', 'top', 'width', 'height', 'right', 'maxHeight'].forEach(function (field) { panel.style[field] = ''; });
13
+ });
14
+ var main = document.getElementById('main-panels');
15
+ if (main) main.classList.remove('has-tool-pane-windows');
16
+ store.set({ toolPaneWindowGeometry: null });
17
+ }
18
+ export function paintLegacyToolPanes(host) {
19
+ var split = store.get('splitPanes');
20
+ if (!host || !split || !split.panes || split.panes.length < 2) return false;
21
+ var panes = Array.from(host.querySelectorAll(':scope > .split-pane:not(.split-pane-cached)'));
22
+ var ids = panes.map(function (pane) {
23
+ var frame = pane.querySelector('.split-pane-frame');
24
+ if (frame && !frame.dataset.toolPaneGeometryReadyBound) {
25
+ frame.dataset.toolPaneGeometryReadyBound = 'true';
26
+ frame.addEventListener('clay-pane-ready', function () { queueMicrotask(function () { if (frame.isConnected) paintLegacyToolPanes(host); }); });
27
+ }
28
+ return frame && frame.dataset.sessionOriginId;
29
+ });
30
+ if (ids.length < split.panes.length || ids.some(function (id) { return !id; }) || new Set(ids).size !== ids.length) return false;
31
+ var base = { id: ids[0] };
32
+ ids.slice(1).forEach(function (id) { base = { axis: 'horizontal', ratio: .5, left: base, right: { id: id } }; });
33
+ var view = activeSavedView() || { id: 'legacy-' + (split.groupId || ids.join('-')), activeSessionOriginId: ids[0] };
34
+ if (view.workspace && view.workspace.linkedPair) base = pairBase(view.workspace.linkedPair, view.tree);
35
+ host.classList.add('split-view-canvas'); host.dataset.positioned = 'true'; delete host.dataset.singlePane;
36
+ var style = getComputedStyle(host), left = parseFloat(style.paddingLeft) || 0, top = parseFloat(style.paddingTop) || 0;
37
+ var width = host.clientWidth - left - (parseFloat(style.paddingRight) || 0), height = host.clientHeight - top - (parseFloat(style.paddingBottom) || 0);
38
+ var tree = toolPaneLayoutTree(view, base), geometry = rectangles(tree, Math.max(0, width), Math.max(0, height), 7);
39
+ paintToolPaneWindows(host, view, base, tree, geometry, { left: left, top: top });
40
+ var focused = store.get('projectSplitViewFocusedOrigin');
41
+ panes.forEach(function (pane, index) {
42
+ var rect = geometry.panes[ids[index]];
43
+ pane.hidden = !!focused && ids[index] !== focused; pane.style.display = pane.hidden ? 'none' : '';
44
+ if (focused === ids[index]) rect = { x: 0, y: 0, width: width, height: height };
45
+ if (!rect) return;
46
+ if (view.sessions && view.sessions.some(function (s) { return s.sessionOriginId === ids[index]; })) ensureArrangeTrigger(pane.querySelector('.split-pane-header'));
47
+ pane.style.left = left + rect.x + 'px'; pane.style.top = top + rect.y + 'px';
48
+ pane.style.width = rect.width + 'px'; pane.style.height = rect.height + 'px';
49
+ });
50
+ host.querySelectorAll(':scope > .split-view-divider').forEach(function (divider) { divider.remove(); });
51
+ (focused ? [] : geometry.dividers).forEach(function (entry) {
52
+ var divider = document.createElement('div'); divider.className = 'split-view-divider'; divider.dataset.axis = entry.axis;
53
+ divider.setAttribute('role', 'separator'); divider.setAttribute('tabindex', '0'); divider.setAttribute('aria-label', 'Resize pane windows');
54
+ divider.setAttribute('aria-orientation', entry.axis === 'horizontal' ? 'vertical' : 'horizontal');
55
+ divider.style.left = left + entry.rect.x + 'px'; divider.style.top = top + entry.rect.y + 'px';
56
+ divider.style.width = entry.rect.width + 'px'; divider.style.height = entry.rect.height + 'px';
57
+ bindWorkspaceDivider(divider, entry, tree); host.appendChild(divider);
58
+ });
59
+ return true;
60
+ }
@@ -0,0 +1,51 @@
1
+ import { WORKBENCH_PANELS } from './workbench-panels.js';
2
+ import { listIds, remove, pairTree, swap } from './split-view-layout-client.js';
3
+
4
+ export var TOOL_PANES = { terminal: { id: 'terminal-container', label: 'Terminal', icon: 'terminal' },
5
+ browser: { id: 'shared-browser-panel', label: 'Browser', icon: 'globe' }, 'file-viewer': { id: 'file-viewer', label: 'Files', icon: 'file-text' } };
6
+ Object.assign(TOOL_PANES, WORKBENCH_PANELS);
7
+ export function toolWindowId(name) { return 'tool-' + name; }
8
+ export function isToolWindow(id) { return /^tool-file-[A-Za-z0-9_-]+$/.test(id) || Object.keys(TOOL_PANES).some(function (name) { return toolWindowId(name) === id; }); }
9
+ export function insertWindow(tree, source, target, side, ratio) {
10
+ if (source === target) return tree;
11
+ if (side === 'center' && isToolWindow(source) && isToolWindow(target)) return swap(tree, source, target).tree;
12
+ side = side === 'center' ? 'right' : side;
13
+ var base = remove(tree, source), found = false;
14
+ function walk(node) {
15
+ if (!node) return node;
16
+ if (node.id) {
17
+ if (node.id !== target) return node;
18
+ found = true;
19
+ var result = pairTree(target, source, side);
20
+ result.ratio = ratio || .5;
21
+ return result;
22
+ }
23
+ return { axis: node.axis, ratio: node.ratio, left: walk(node.left), right: walk(node.right) };
24
+ }
25
+ var next = walk(base);
26
+ return found && listIds(next).length <= 24 ? next : tree;
27
+ }
28
+ function stripUnknown(tree, allowed) {
29
+ var result = tree;
30
+ listIds(tree).forEach(function (id) { if (allowed.indexOf(id) < 0) result = remove(result, id); });
31
+ return result;
32
+ }
33
+ function placements(tree, output) {
34
+ if (!tree || tree.id) return;
35
+ var left = listIds(tree.left), right = listIds(tree.right);
36
+ if (tree.left.id && isToolWindow(tree.left.id)) output.push({ id: tree.left.id, target: right[0], side: tree.axis === 'horizontal' ? 'left' : 'top', ratio: tree.ratio });
37
+ else if (tree.right.id && isToolWindow(tree.right.id)) output.push({ id: tree.right.id, target: left[0], side: tree.axis === 'horizontal' ? 'right' : 'bottom', ratio: tree.ratio });
38
+ placements(tree.left, output); placements(tree.right, output);
39
+ }
40
+ export function workspaceWindowTree(base, saved, owners) {
41
+ var sessions = listIds(base), tools = owners.map(toolWindowId), allowed = sessions.concat(tools), tree = base;
42
+ if (saved && saved.tree && JSON.stringify(saved.base) === JSON.stringify(base)) tree = stripUnknown(saved.tree, allowed);
43
+ else if (saved && saved.tree) {
44
+ var attached = []; placements(saved.tree, attached);
45
+ attached.reverse().forEach(function (item) { if (tools.indexOf(item.id) >= 0 && listIds(tree).indexOf(item.target) >= 0) tree = insertWindow(tree, item.id, item.target, item.side, item.ratio); });
46
+ }
47
+ tools.forEach(function (id) {
48
+ if (listIds(tree).indexOf(id) < 0) tree = tree ? { axis: 'horizontal', ratio: .66, left: tree, right: { id: id } } : { id: id };
49
+ });
50
+ return tree;
51
+ }
@@ -0,0 +1,16 @@
1
+ import { copy, mapWorkspaceTree, paneOwner } from './view-workspace-state.js';
2
+ import { listIds } from './split-view-layout-client.js';
3
+ // Coordinates never create resources. Resolve the old singleton Files slot only
4
+ // after a real file instance exists; leave other delayed slots intact.
5
+ export function boundLegacyCoordinates(record, value) {
6
+ var next = copy(record), mapping = {};
7
+ listIds(record.tree).forEach(function (id) { mapping[id] = id; });
8
+ var files = value.panes.filter(function (pane) { return pane.kind === 'file' && pane.open && pane.placement === 'docked'; });
9
+ if (files.length === 1) mapping['tool-file-viewer'] = 'tool-' + paneOwner(files[0]);
10
+ next.tree = mapWorkspaceTree(next.tree, mapping);
11
+ return next;
12
+ }
13
+ export function legacyCoordinatesReady(record, value) {
14
+ var ids = value.panes.filter(function (p) { return p.open && p.placement === 'docked'; }).map(function (p) { return 'tool-' + paneOwner(p); });
15
+ return listIds(boundLegacyCoordinates(record, value).tree).filter(function (id) { return id.indexOf('tool-') === 0; }).every(function (id) { return ids.indexOf(id) >= 0; });
16
+ }