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
@@ -0,0 +1,18 @@
1
+ import { store } from './store.js';
2
+
3
+ // Read-only presentation metadata from this pane's committed authenticated view.
4
+ export function paneHeaderRuntime() {
5
+ var state = store.snap(), binding = state.paneCommittedBinding;
6
+ if (!binding || !state.connected || !state.paneReady || state.paneSuspended || state.panePendingBinding ||
7
+ state.activeSessionId !== binding.sessionId || state.sessionOriginId !== binding.sessionOriginId ||
8
+ state.currentSlug !== binding.projectSlug || (state.myUserId || null) !== (binding.viewerId || null)) return null;
9
+ return { binding: binding, vendor: state.currentVendor, model: state.currentModel || '' };
10
+ }
11
+
12
+ export function initPaneHeaderRuntime(frame) {
13
+ store.subscribe(function (state, previous) {
14
+ if (state.currentModel === previous.currentModel && state.currentVendor === previous.currentVendor) return;
15
+ if (!paneHeaderRuntime() || frame.contentWindow !== window || frame.contentDocument !== document) return;
16
+ frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-config'));
17
+ });
18
+ }
@@ -6,17 +6,17 @@ export function isPlantUmlFile(path) {
6
6
  return /\.(puml|plantuml|pu|uml|salt)$/i.test(String(path || ""));
7
7
  }
8
8
 
9
- export function cancelPlantUmlPreview() {
10
- var job = store.get('plantUmlPreviewJob');
9
+ export function cancelPlantUmlPreview(instanceId) {
10
+ var job = store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob');
11
11
  if (!job) return;
12
12
  clearTimeout(job.timer);
13
13
  job.observer.disconnect();
14
14
  job.controller.abort();
15
- store.set({ plantUmlPreviewJob: null });
15
+ var cleared = {}; cleared[instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob'] = null; store.set(cleared);
16
16
  }
17
17
 
18
- export function renderPlantUmlPreview(body, source, path) {
19
- cancelPlantUmlPreview();
18
+ export function renderPlantUmlPreview(body, source, path, instanceId) {
19
+ cancelPlantUmlPreview(instanceId);
20
20
  body.innerHTML = "";
21
21
  var preview = document.createElement("div");
22
22
  preview.className = "file-viewer-plantuml-preview";
@@ -36,22 +36,22 @@ export function renderPlantUmlPreview(body, source, path) {
36
36
 
37
37
  var job = { controller: new AbortController(), observer: null, timer: null };
38
38
  job.observer = new MutationObserver(function () {
39
- if (!preview.isConnected && store.get('plantUmlPreviewJob') === job) cancelPlantUmlPreview();
39
+ if (!preview.isConnected && store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') === job) cancelPlantUmlPreview(instanceId);
40
40
  });
41
41
  job.observer.observe(body, { childList: true });
42
42
  job.timer = setTimeout(function () {
43
- if (store.get('plantUmlPreviewJob') !== job) return;
43
+ if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') !== job) return;
44
44
  showError("The diagram preview timed out.");
45
- cancelPlantUmlPreview();
45
+ cancelPlantUmlPreview(instanceId);
46
46
  }, 20000);
47
- store.set({ plantUmlPreviewJob: job });
47
+ var patch = {}; patch[instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob'] = job; store.set(patch);
48
48
  fetch("api/file/plantuml?path=" + encodeURIComponent(path), { signal: job.controller.signal, cache: "no-store" }).then(function (response) {
49
49
  return response.text().then(function (text) {
50
50
  if (!response.ok) throw new Error(text || "Unable to render this diagram.");
51
51
  return text;
52
52
  });
53
53
  }).then(function (svg) {
54
- if (store.get('plantUmlPreviewJob') !== job || !preview.isConnected) return;
54
+ if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') !== job || !preview.isConnected) return;
55
55
  if (svg.length > 8 * 1024 * 1024 || !/<svg[\s>]/i.test(svg)) throw new Error("The renderer returned an invalid diagram.");
56
56
  var image = document.createElement("img");
57
57
  image.alt = "Diagram preview of " + path;
@@ -69,8 +69,8 @@ export function renderPlantUmlPreview(body, source, path) {
69
69
  mountWireframeViewport(viewport, image, toolbar);
70
70
 
71
71
  }).catch(function (error) {
72
- if (store.get('plantUmlPreviewJob') === job && error.name !== "AbortError") showError(error.message);
72
+ if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') === job && error.name !== "AbortError") showError(error.message);
73
73
  }).finally(function () {
74
- if (store.get('plantUmlPreviewJob') === job) cancelPlantUmlPreview();
74
+ if (store.get(instanceId ? 'plantUmlPreviewJob:' + instanceId : 'plantUmlPreviewJob') === job) cancelPlantUmlPreview(instanceId);
75
75
  });
76
76
  }
@@ -1,14 +1,15 @@
1
- // Single arbitration point for the mutually exclusive right-side workbench.
1
+ import { WORKBENCH_PANELS } from './workbench-panels.js';
2
+ import { activeSavedView, workspaceChanged } from './view-workspace-state.js';
3
+ // Shared arbitration for docked background panes and one foreground overlay.
2
4
 
5
+ import { renderToolPaneChrome } from './tool-pane-chrome.js';
3
6
  import { store } from './store.js';
4
7
  import { iconHtml, refreshIcons } from './icons.js';
5
8
  import { showToast } from './utils.js';
6
9
 
7
10
  var closers = {};
8
- var panelIds = { terminal: 'terminal-container', browser: 'shared-browser-panel', 'file-viewer': 'file-viewer',
9
- 'project-logs': 'project-logs-panel', you: 'you-panel', issues: 'issues-panel', linear: 'linear-panel',
10
- 'notes-browser': 'notes-browser', 'scheduled-tasks': 'scheduled-tasks-panel', debates: 'debates-workbench',
11
- 'mcp-skills': 'mcp-skills-workbench', 'mate-knowledge': 'mate-knowledge-workbench', wireframe: 'wireframe-panel' };
11
+ var panelIds = { terminal: 'terminal-container', browser: 'shared-browser-panel', 'file-viewer': 'file-viewer' };
12
+ Object.keys(WORKBENCH_PANELS).forEach(function (name) { panelIds[name] = WORKBENCH_PANELS[name].id; });
12
13
 
13
14
  // Pane startup does not need account settings for a closed workbench.
14
15
  function ensureLayoutPreference() {
@@ -25,88 +26,136 @@ function ensureLayoutPreference() {
25
26
  });
26
27
  }
27
28
 
29
+ function dockedOwners() { return store.get('rightWorkbenchDockedOwners') || []; }
30
+ function openOwner(name) { return store.get('rightWorkbenchOwner') === name || dockedOwners().indexOf(name) !== -1; }
31
+ export function rightWorkbenchRevisionFor(name) {
32
+ var revisions = store.get('rightWorkbenchPanelRevisions') || {};
33
+ return revisions[name] == null ? store.get('rightWorkbenchRevision') || 0 : revisions[name];
34
+ }
35
+ function publish(owner, docked, revisions) {
36
+ store.set({ rightWorkbenchOwner: owner, rightWorkbenchDockedOwners: docked,
37
+ rightWorkbenchPanelRevisions: revisions, rightWorkbenchRevision: (store.get('rightWorkbenchRevision') || 0) + 1 });
38
+ workspaceChanged();
39
+ }
40
+ function setDocked(name, docked) {
41
+ if (!openOwner(name)) return;
42
+ var owners = dockedOwners().filter(function (item) { return item !== name; }), previous = store.get('rightWorkbenchOwner');
43
+ if (docked) owners.push(name);
44
+ publish(docked ? previous : name, owners, store.get('rightWorkbenchPanelRevisions') || {});
45
+ if (!docked && previous && previous !== name && owners.indexOf(previous) === -1 && closers[previous]) closers[previous]();
46
+ }
28
47
  function renderLayoutControls() {
29
48
  if (typeof document === 'undefined') return;
30
49
  var main = document.getElementById('main-panels');
31
50
  if (!main) return;
32
- var state = store.get('workbenchLayoutPreference') || {}, docked = state.mode === 'docked';
33
- main.dataset.workbenchMode = docked ? 'docked' : 'overlay';
34
- var panel = document.getElementById(panelIds[store.get('rightWorkbenchOwner')]);
35
- if (!panel) return;
36
- var header = panel.querySelector(':scope > header, :scope > .file-viewer-header .file-viewer-tabbar, :scope > .terminal-header');
37
- if (!header) return;
38
- var button = panel.querySelector('.workbench-layout-toggle');
39
- if (!button) {
40
- var actions = header.querySelector('[class*="window-actions"], .terminal-header-actions, .shared-browser-window, :scope.mcp-skills-header > div') || header;
41
- button = document.createElement('button'); button.type = 'button'; button.className = 'workbench-layout-toggle';
42
- button.addEventListener('click', function () {
43
- var current = store.get('workbenchLayoutPreference') || {};
44
- if (!current.ready || current.loading || current.saving) { ensureLayoutPreference(); return; }
45
- var owner = store.get('myUserId'), project = store.get('currentSlug');
46
- function currentTarget() { return button.isConnected && owner === store.get('myUserId') && project === store.get('currentSlug'); }
47
- import('./workbench-layout-preference.js').then(function (api) {
48
- var fresh = store.get('workbenchLayoutPreference') || {};
49
- if (!currentTarget() || fresh.mode !== current.mode || fresh.revision !== current.revision || fresh.loading || fresh.saving || !fresh.ready) return false;
50
- return api.saveWorkbenchLayoutMode(current.mode === 'docked' ? 'overlay' : 'docked');
51
- }).then(function (saved) {
52
- if (saved === false && currentTarget()) showToast((store.get('workbenchLayoutPreference') || {}).error || 'Workbench layout could not be saved. Try again.', 'error');
53
- }).catch(function (error) {
54
- if (currentTarget()) showToast(error.message || 'Workbench layout could not be saved. Try again.', 'error');
51
+ var owners = dockedOwners(), state = store.get('workbenchLayoutPreference') || {};
52
+ main.dataset.workbenchMode = owners.length ? 'docked' : 'overlay';
53
+ main.dataset.workbenchDocks = String(owners.length);
54
+ Object.keys(panelIds).forEach(function (name) {
55
+ var panel = document.getElementById(panelIds[name]);
56
+ if (!panel) return;
57
+ var docked = owners.indexOf(name) !== -1;
58
+ renderToolPaneChrome(panel, name);
59
+ panel.dataset.workbenchPlacement = docked ? 'docked' : 'overlay';
60
+ panel.dataset.workbenchActive = store.get('rightWorkbenchOwner') === name ? 'true' : 'false';
61
+ if (!openOwner(name)) return;
62
+ var header = panel.querySelector(':scope > .tool-pane-header, :scope > header, :scope > .file-viewer-header .file-viewer-tabbar, :scope > .terminal-header');
63
+ if (!header) return;
64
+ var button = panel.querySelector('.workbench-layout-toggle');
65
+ if (!button) {
66
+ var actions = header.querySelector('[class*="window-actions"], .terminal-header-actions, .shared-browser-window, .file-viewer-window-actions, .workbench-pane-actions, :scope.mcp-skills-header > div') || header;
67
+ button = document.createElement('button'); button.type = 'button'; button.className = 'workbench-layout-toggle';
68
+ button.addEventListener('click', function () {
69
+ if (activeSavedView()) { setDocked(name, dockedOwners().indexOf(name) < 0); return; }
70
+ var current = store.get('workbenchLayoutPreference') || {};
71
+ if (!current.ready || current.loading || current.saving) { ensureLayoutPreference(); return; }
72
+ var owner = store.get('myUserId'), project = store.get('currentSlug'), revision = rightWorkbenchRevisionFor(name);
73
+ var wasDocked = dockedOwners().indexOf(name) !== -1;
74
+ function currentTarget() { return button.isConnected && openOwner(name) && owner === store.get('myUserId') && project === store.get('currentSlug') &&
75
+ revision === rightWorkbenchRevisionFor(name) && wasDocked === (dockedOwners().indexOf(name) !== -1); }
76
+ import('./workbench-layout-preference.js').then(function (api) {
77
+ var fresh = store.get('workbenchLayoutPreference') || {};
78
+ if (!currentTarget() || fresh.mode !== current.mode || fresh.revision !== current.revision || fresh.loading || fresh.saving || !fresh.ready) return false;
79
+ return api.saveWorkbenchLayoutMode(wasDocked ? 'overlay' : 'docked');
80
+ }).then(function (saved) {
81
+ if (!currentTarget()) return;
82
+ var fresh = store.get('workbenchLayoutPreference') || {};
83
+ if (saved === true && fresh.ready && fresh.mode === (wasDocked ? 'overlay' : 'docked')) setDocked(name, !wasDocked);
84
+ else if (saved === false) showToast(fresh.error || 'Workbench layout could not be saved. Try again.', 'error');
85
+ }).catch(function (error) {
86
+ if (currentTarget()) showToast(error.message || 'Workbench layout could not be saved. Try again.', 'error');
87
+ });
55
88
  });
56
- });
57
- var anchor = actions === header ? header.querySelector('[data-knowledge-wide]') : actions.firstElementChild;
58
- actions.insertBefore(button, anchor || null);
59
- }
60
- if (button.dataset.mode !== (docked ? 'docked' : 'overlay')) {
61
- button.dataset.mode = docked ? 'docked' : 'overlay';
62
- button.innerHTML = iconHtml(docked ? 'layers-2' : 'panel-right');
63
- refreshIcons();
64
- }
65
- button.setAttribute('aria-label', docked ? 'Float workbench over sessions' : 'Dock workbench beside sessions');
66
- button.title = state.error || button.getAttribute('aria-label');
67
- button.setAttribute('aria-pressed', docked ? 'true' : 'false');
68
- button.disabled = !!state.loading || !!state.saving;
69
- button.setAttribute('aria-busy', state.loading || state.saving ? 'true' : 'false');
89
+ var anchor = actions === header ? header.querySelector('[data-knowledge-wide]') : actions.firstElementChild;
90
+ if (panel.dataset.fileInstance) { var pin = actions.querySelector('[data-action="pin"]'); anchor = pin && pin.nextSibling; }
91
+ actions.insertBefore(button, anchor || null);
92
+ }
93
+ if (button.dataset.mode !== (docked ? 'docked' : 'overlay')) {
94
+ button.dataset.mode = docked ? 'docked' : 'overlay';
95
+ button.innerHTML = iconHtml(docked ? 'layers-2' : 'panel-right'); refreshIcons();
96
+ }
97
+ button.setAttribute('aria-label', docked ? 'Float workbench over sessions' : 'Dock workbench beside sessions');
98
+ button.title = state.error || button.getAttribute('aria-label');
99
+ button.setAttribute('aria-pressed', docked ? 'true' : 'false');
100
+ // View edits are queued by the workspace controller. Disabling this button
101
+ // for an unrelated in-flight save can swallow a click between down and up.
102
+ button.disabled = activeSavedView() ? false : !!state.loading || !!state.saving;
103
+ button.setAttribute('aria-busy', state.loading || state.saving ? 'true' : 'false');
104
+ });
70
105
  }
71
106
 
72
- export function registerRightWorkbench(name, close) {
107
+ export function registerRightWorkbench(name, close, panelId) {
108
+ if (panelId) panelIds[name] = panelId;
73
109
  if (!name || typeof close !== "function") return;
74
110
  closers[name] = close;
75
111
  }
76
112
 
113
+ export function unregisterRightWorkbench(name) {
114
+ delete closers[name];
115
+ if (name.indexOf('file-') === 0 && name !== 'file-viewer') delete panelIds[name];
116
+ }
77
117
  export function claimRightWorkbench(name) {
78
118
  if (typeof document !== 'undefined') ensureLayoutPreference();
79
- var previousRevision = store.get("rightWorkbenchRevision") || 0;
80
- var nextRevision = previousRevision + 1;
81
- storeSet(name, nextRevision);
82
- var keys = Object.keys(closers);
83
- for (var i = 0; i < keys.length; i++) {
84
- if (keys[i] === name) continue;
85
- try { closers[keys[i]](); } catch (error) { /* one panel must not block another */ }
86
- }
119
+ var owner = store.get('rightWorkbenchOwner'), docked = dockedOwners().slice();
120
+ var retained = docked.indexOf(name) !== -1 && owner && docked.indexOf(owner) === -1 ? owner : name;
121
+ var revision = (store.get('rightWorkbenchRevision') || 0) + 1, revisions = Object.assign({}, store.get('rightWorkbenchPanelRevisions') || {});
122
+ Object.keys(revisions).forEach(function (key) { if (key !== retained && docked.indexOf(key) === -1) delete revisions[key]; });
123
+ revisions[name] = revision;
124
+ publish(retained, docked, revisions);
125
+ Object.keys(closers).forEach(function (key) {
126
+ if (key === name || key === retained || docked.indexOf(key) !== -1) return;
127
+ try { closers[key](); } catch (error) { /* one panel must not block another */ }
128
+ });
87
129
  if (typeof requestAnimationFrame === 'function') requestAnimationFrame(renderLayoutControls);
88
- return nextRevision;
89
- }
90
-
91
- function storeSet(name, value) {
92
- store.set({ rightWorkbenchOwner: name, rightWorkbenchRevision: value });
130
+ return revision;
93
131
  }
94
-
95
132
  export function releaseRightWorkbench(name) {
96
- if (store.get("rightWorkbenchOwner") !== name) return;
97
- storeSet(null, (store.get("rightWorkbenchRevision") || 0) + 1);
133
+ if (!openOwner(name)) return;
134
+ var docked = dockedOwners().filter(function (item) { return item !== name; }), revisions = Object.assign({}, store.get('rightWorkbenchPanelRevisions') || {});
135
+ delete revisions[name];
136
+ publish(store.get('rightWorkbenchOwner') === name ? docked[docked.length - 1] || null : store.get('rightWorkbenchOwner'), docked, revisions);
98
137
  }
99
-
100
138
  export function isRightWorkbenchCurrent(name, expectedRevision) {
101
- return store.get("rightWorkbenchOwner") === name && store.get("rightWorkbenchRevision") === expectedRevision;
139
+ return openOwner(name) && rightWorkbenchRevisionFor(name) === expectedRevision;
102
140
  }
103
-
104
141
  export function resetRightWorkbench() {
105
- storeSet(null, (store.get("rightWorkbenchRevision") || 0) + 1);
142
+ publish(null, [], {});
143
+ Object.keys(closers).forEach(function (name) { try { closers[name](); } catch (error) { /* Clear other panes after a context change. */ } });
106
144
  }
107
-
108
145
  store.subscribe(function (state, previous) {
109
- if (state.workbenchLayoutPreference !== previous.workbenchLayoutPreference || state.rightWorkbenchOwner !== previous.rightWorkbenchOwner) renderLayoutControls();
110
- if (state.currentSlug === previous.currentSlug && state.myUserId === previous.myUserId) return;
111
- resetRightWorkbench();
146
+ if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId) { resetRightWorkbench(); return; }
147
+ if (state.projectSplitViewPending !== previous.projectSplitViewPending || state.workbenchLayoutPreference !== previous.workbenchLayoutPreference || state.rightWorkbenchOwner !== previous.rightWorkbenchOwner ||
148
+ state.rightWorkbenchDockedOwners !== previous.rightWorkbenchDockedOwners) renderLayoutControls();
112
149
  });
150
+
151
+ export function restoreWorkbenchDock(name) { setDocked(name, true); }
152
+
153
+ // Document-level shortcuts belong to the focused pane, then the foreground overlay.
154
+ export function handlesWorkbenchKey(name, event) {
155
+ if (event.defaultPrevented) return false;
156
+ var target = event.target, focused = Object.keys(panelIds).find(function (owner) {
157
+ var panel = document.getElementById(panelIds[owner]);
158
+ return panel && target && panel.contains(target);
159
+ });
160
+ return (focused || store.get('rightWorkbenchOwner')) === name;
161
+ }
@@ -65,18 +65,19 @@ function ensurePanel() {
65
65
  panel.innerHTML =
66
66
  '<header class="scheduled-tasks-topbar">' +
67
67
  '<span class="scheduled-tasks-title">' + iconHtml("calendar-clock") + 'Scheduled</span>' +
68
- '<div class="scheduled-tasks-create-group">' +
69
- '<button class="scheduled-tasks-new" type="button" data-action="new">' + iconHtml("plus") + 'New</button>' +
70
- '<button id="scheduled-tasks-create-menu-trigger" class="scheduled-tasks-new-menu" type="button" data-action="new-menu" aria-label="More task creation options" aria-haspopup="menu" aria-expanded="false" aria-controls="scheduled-tasks-create-menu">' + iconHtml("chevron-down") + '</button>' +
71
- '<div id="scheduled-tasks-create-menu" class="scheduled-tasks-create-menu hidden" role="menu" aria-labelledby="scheduled-tasks-create-menu-trigger"><button type="button" role="menuitem" data-action="manual">Manual creation</button></div>' +
72
- '</div>' +
73
68
  '<div class="scheduled-tasks-window-actions">' +
74
69
  '<button class="scheduled-tasks-icon-btn" type="button" data-action="wide" title="Widen panel" aria-label="Widen Scheduled panel" aria-pressed="false">' + iconHtml("chevrons-left-right") + '</button>' +
75
70
  '<button class="scheduled-tasks-icon-btn" type="button" data-action="fullscreen" title="Toggle fullscreen" aria-label="Toggle Scheduled fullscreen" aria-pressed="false">' + iconHtml("maximize-2") + '</button>' +
76
71
  '<button class="scheduled-tasks-icon-btn" type="button" data-action="close" title="Close" aria-label="Close Scheduled">' + iconHtml("x") + '</button>' +
77
72
  '</div>' +
78
73
  '</header>' +
79
- '<div class="scheduled-task-default-ai"><span>New interviews use your shared Default AI.</span><button type="button" data-action="default-ai">Change default</button></div>' +
74
+ '<div class="scheduled-tasks-toolbar" role="toolbar" aria-label="Scheduled task actions">' +
75
+ '<div class="scheduled-tasks-create-group">' +
76
+ '<button class="scheduled-tasks-new" type="button" data-action="new">' + iconHtml("plus") + 'New</button>' +
77
+ '<button id="scheduled-tasks-create-menu-trigger" class="scheduled-tasks-new-menu" type="button" data-action="new-menu" aria-label="More task creation options" aria-haspopup="menu" aria-expanded="false" aria-controls="scheduled-tasks-create-menu">' + iconHtml("chevron-down") + '</button>' +
78
+ '<div id="scheduled-tasks-create-menu" class="scheduled-tasks-create-menu hidden" role="menu" aria-labelledby="scheduled-tasks-create-menu-trigger"><button type="button" role="menuitem" data-action="manual">Manual creation</button></div>' +
79
+ '</div>' +
80
+ '<div class="scheduled-task-default-ai"><span>New interviews use your shared Default AI.</span><button type="button" data-action="default-ai">Change default</button></div></div>' +
80
81
  '<div class="scheduled-task-draft-slot"></div>' +
81
82
  '<div class="scheduled-tasks-list" role="list"></div>';
82
83
  host.appendChild(panel);
@@ -1,4 +1,6 @@
1
+ import { recordToolExpansion } from './view-presentation-state.js';
1
2
  import { registerStudioPaint } from './clay-studio-render.js';
3
+ import { hasBrowserPaneHost, initBrowserPaneTransport, forwardBrowserPaneMessage, sendBrowserPaneCommand, ensureBrowserPaneSource, browserPaneSessionId, browserPaneStamp, browserPaneBound } from './pane-browser-transport.js';
2
4
  import { browserLinkTarget, initBrowserLinks } from './clay-browser-links.js';
3
5
  import { browserSettingsHtml, bindBrowserSettings, renderBrowserSettings, closeBrowserSettings, handleBrowserSettingsMessage } from './shared-browser-settings.js';
4
6
  import { renderBrowserTabs } from './shared-browser-tabs.js';
@@ -19,9 +21,11 @@ function eligible() { return visibleEntry() && !!store.get('activeSessionId'); }
19
21
  function panel() { return document.getElementById('shared-browser-panel'); }
20
22
  function ui(patch) { store.set({ sharedBrowserUi: Object.assign({}, store.get('sharedBrowserUi'), patch) }); }
21
23
  function send(type, fields) {
24
+ var browser = store.get('sharedBrowser');
25
+ var paneSent = sendBrowserPaneCommand(type, Object.assign({ browserId: browser && browser.id, epoch: browser && browser.epoch }, fields || {}));
26
+ if (paneSent !== null) return paneSent;
22
27
  var ws = getWs();
23
28
  if (!ws || ws.readyState !== 1 || !store.get('activeSessionId')) return false;
24
- var browser = store.get('sharedBrowser');
25
29
  ws.send(JSON.stringify(Object.assign({ type: 'shared_browser_' + type, sessionId: store.get('activeSessionId'),
26
30
  browserId: browser && browser.id, epoch: browser && browser.epoch }, fields || {})));
27
31
  return true;
@@ -65,7 +69,7 @@ function ensurePanel() {
65
69
  root.appendChild(el);
66
70
  el.querySelector('[data-action="hide"]').onclick = hideSharedBrowser;
67
71
  el.querySelector('[data-action="wide"]').onclick = function () { var wide = !(store.get('sharedBrowserUi') || {}).wide; ui({ wide: wide }); render(); };
68
- el.querySelector('[data-action="fullscreen"]').onclick = function () { var full = !(store.get('sharedBrowserUi') || {}).fullscreen; ui({ fullscreen: full }); render(); };
72
+ el.querySelector('[data-action="fullscreen"]').onclick = function () { var full = !(store.get('sharedBrowserUi') || {}).fullscreen; setBrowserExpanded(full); recordToolExpansion('browser', full); };
69
73
  ['back', 'forward', 'reload'].forEach(function (action) { el.querySelector('[data-action="' + action + '"]').onclick = function () { sendBrowserInput({ kind: action }); }; });
70
74
  el.querySelector('form').onsubmit = function (event) { event.preventDefault(); sendBrowserInput({ kind: 'navigate', url: el.querySelector('form input').value }); };
71
75
  el.querySelector('[data-action="control"]').onclick = function () {
@@ -82,12 +86,18 @@ function ensurePanel() {
82
86
  }
83
87
  function renderEntry() {
84
88
  var browser = store.get('sharedBrowser');
89
+ var count = (store.get('sharedBrowsers') || []).length;
85
90
  var ready = !!store.get('connected') && !!store.get('activeSessionId');
86
91
  var label = !store.get('connected') ? 'Browser · Connecting…' : !store.get('activeSessionId') ? 'Browser · Loading session…' : browser ? 'View browser' : 'Open browser';
87
92
  ['shared-browser-btn', 'shared-browser-toggle-btn'].forEach(function (id) {
88
93
  var button = document.getElementById(id);
89
94
  if (!button) return;
90
95
  var title = id === 'shared-browser-toggle-btn' && ready && store.get('sharedBrowserOpen') ? 'Hide browser' : label;
96
+ if (id === 'shared-browser-toggle-btn') {
97
+ var badge = button.querySelector('.shared-browser-count');
98
+ if (badge) { badge.textContent = String(count); badge.classList.toggle('hidden', count === 0); }
99
+ if (count) title += ' · ' + count + (count === 1 ? ' tab' : ' tabs');
100
+ }
91
101
  button.classList.toggle('hidden', !visibleEntry());
92
102
  button.title = ready && browser ? title + ' · ' + browser.phase : title;
93
103
  button.setAttribute('aria-label', title);
@@ -172,7 +182,28 @@ function selectBrowserTab(id) {
172
182
  setBrowserTab(browser);
173
183
  send('view', { visible: desktop() && !document.hidden }); render();
174
184
  }
185
+ function browserOpenContext() {
186
+ return JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('projectSplitViewActiveId'), store.get('sessionOriginId'), store.get('projectSplitViewSocketGeneration')]);
187
+ }
188
+ function sendOrQueueBrowserOpen(fields) {
189
+ store.set({ pendingBrowserOpen: null });
190
+ if (send('open', fields)) return;
191
+ // A false transport result means nothing was sent. Only this fresh human
192
+ // opening can wait for pane readiness; restoration never enters this path.
193
+ store.set({ pendingBrowserOpen: { context: browserOpenContext(), socket: getWs(), fields: fields || {}, expires: Date.now() + 15000 } });
194
+ }
195
+ function resumePendingBrowserOpen() {
196
+ var pending = store.get('pendingBrowserOpen');
197
+ if (!pending) return;
198
+ if (pending.context !== browserOpenContext() || pending.socket !== getWs() || pending.expires < Date.now() || !store.get('connected') || !store.get('sharedBrowserOpen')) {
199
+ store.set({ pendingBrowserOpen: null }); return;
200
+ }
201
+ if (!ensureBrowserPaneSource()) return;
202
+ store.set({ pendingBrowserOpen: null });
203
+ if (!send('open', pending.fields)) store.set({ pendingBrowserOpen: pending });
204
+ }
175
205
  export function openSharedBrowserAt(value) {
206
+ ensureBrowserPaneSource();
176
207
  var url = browserLinkTarget('browser:' + value);
177
208
  if (!url) throw new Error('Enter a valid HTTP or HTTPS browser address.');
178
209
  if (!desktop()) throw new Error('Clay Browser is available on desktop screens.');
@@ -180,19 +211,22 @@ export function openSharedBrowserAt(value) {
180
211
  if (!store.get('connected')) throw new Error('Reconnect before opening this browser link.');
181
212
  ensurePanel(); closeBrowserSettings(false);
182
213
  claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
183
- ui({ error: '' }); send('open', { url: url }); render();
214
+ ui({ error: '' }); sendOrQueueBrowserOpen({ url: url }); render();
184
215
  }
185
216
  export function openSharedBrowser() {
217
+ ensureBrowserPaneSource();
186
218
  if (!eligible() || !store.get('connected')) return;
187
219
  ensurePanel();
188
220
  claimRightWorkbench('browser');
189
221
  store.set({ sharedBrowserOpen: true });
190
222
  var browser = store.get('sharedBrowser');
191
- if (!browser) send('open');
223
+ if (!browser) sendOrQueueBrowserOpen();
192
224
  else send('view', { visible: true });
193
225
  render();
194
226
  }
195
227
  export function hideSharedBrowser() {
228
+ setBrowserExpanded(false); recordToolExpansion('browser', false);
229
+ if (!store.get('savedWorkspaceApplying')) store.set({ pendingBrowserOpen: null });
196
230
  closeViewportMenu();
197
231
  closeBrowserSettings();
198
232
  clearBrowserInput();
@@ -201,9 +235,11 @@ export function hideSharedBrowser() {
201
235
  releaseRightWorkbench('browser');
202
236
  render();
203
237
  }
204
- export function handleSharedBrowserMessage(msg) {
238
+ export function handleSharedBrowserMessage(msg, fromPane) {
205
239
  if (!msg.type || msg.type.indexOf('shared_browser_') !== 0) return false;
206
- if (String(msg.sessionId) !== String(store.get('activeSessionId'))) return true;
240
+ if (forwardBrowserPaneMessage(msg)) return true;
241
+ if (browserPaneBound() && !fromPane) return true;
242
+ if (String(msg.sessionId) !== String(browserPaneSessionId() || store.get('activeSessionId'))) return true;
207
243
  if (handleBrowserSettingsMessage(msg)) return true;
208
244
  if (msg.type === 'shared_browser_state') {
209
245
  var old = store.get('sharedBrowser');
@@ -212,6 +248,7 @@ export function handleSharedBrowserMessage(msg) {
212
248
  var selected = !msg.focus && old && tabs.find(function (tab) { return tab.id === old.id; });
213
249
  selected = selected || msg.browser || tabs[0] || null;
214
250
  setBrowserTab(selected);
251
+ store.set({ savedViewBrowserListReady: true });
215
252
  if (msg.present && eligible()) {
216
253
  ensurePanel(); claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
217
254
  }
@@ -222,23 +259,35 @@ export function handleSharedBrowserMessage(msg) {
222
259
  var current = store.get('sharedBrowser');
223
260
  if (!desktop() || !store.get('sharedBrowserOpen') || !current || current.id !== msg.browserId || (store.get('sharedBrowserUi') || {}).decoding) return true;
224
261
  var sessionId = store.get('activeSessionId');
262
+ var paneStamp = browserPaneStamp();
225
263
  ui({ decoding: true });
226
264
  var image = new Image();
227
265
  image.onload = function () {
228
266
  var latest = store.get('sharedBrowser');
229
- if (latest && latest.id === msg.browserId && store.get('activeSessionId') === sessionId && panel()) {
267
+ if (latest && latest.id === msg.browserId && browserPaneStamp() === paneStamp && store.get('activeSessionId') === sessionId && panel()) {
230
268
  var canvas = panel().querySelector('canvas'); canvas.width = image.naturalWidth; canvas.height = image.naturalHeight;
231
269
  canvas.getContext('2d').drawImage(image, 0, 0);
232
270
  ui({ decoding: false, hasFrame: true }); render();
233
271
  registerStudioPaint(msg, canvas, null, canvas);
234
272
  }
235
273
  };
236
- image.onerror = function () { if (store.get('activeSessionId') === sessionId) ui({ decoding: false }); };
274
+ image.onerror = function () { if (browserPaneStamp() === paneStamp && store.get('activeSessionId') === sessionId) ui({ decoding: false }); };
237
275
  image.src = 'data:image/jpeg;base64,' + msg.data;
238
276
  }
239
277
  return true;
240
278
  }
241
279
  export function initSharedBrowser() {
280
+ initBrowserPaneTransport(handleSharedBrowserMessage, function (available) {
281
+ var applying = store.get('savedWorkspaceApplying');
282
+ store.set({ savedWorkspaceApplying: true, savedViewBrowserListReady: false });
283
+ try {
284
+ clearBrowserInput(); setBrowserTab(null);
285
+ store.set({ sharedBrowsers: [], sharedBrowserUi: {}, sharedBrowserSettings: {} });
286
+ if (!available) hideSharedBrowser();
287
+ render();
288
+ } finally { store.set({ savedWorkspaceApplying: applying }); }
289
+ });
290
+ document.addEventListener('clay-pane-ready', function () { queueMicrotask(resumePendingBrowserOpen); }, true);
242
291
  initBrowserLinks();
243
292
  var button = document.getElementById('shared-browser-btn');
244
293
  if (button) button.onclick = openSharedBrowser;
@@ -255,14 +304,31 @@ export function initSharedBrowser() {
255
304
  registerRightWorkbench('browser', hideSharedBrowser);
256
305
  store.subscribe(function (state, previous) {
257
306
  if (state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.myUserId !== previous.myUserId) {
307
+ if (browserPaneSessionId() !== null) { render(); return; }
258
308
  clearBrowserInput(); releaseRightWorkbench('browser');
259
- store.set({ sharedBrowser: null, sharedBrowsers: [], sharedBrowserOpen: false, sharedBrowserUi: {}, sharedBrowserSettings: {} });
309
+ store.set({ sharedBrowser: null, sharedBrowsers: [], savedViewBrowserListReady: false, sharedBrowserOpen: false, sharedBrowserUi: {}, sharedBrowserSettings: {} });
260
310
  if (state.connected) send('state_request');
261
311
  render();
262
312
  } else if (state.connected !== previous.connected) {
313
+ store.set({ savedViewBrowserListReady: false });
263
314
  if (state.connected) send('state_request');
264
315
  render();
265
316
  } else if (state.permissions !== previous.permissions || state.dmMode !== previous.dmMode) {
266
317
  if (!eligible()) hideSharedBrowser(); renderEntry();
267
318
  }
268
319
  });
320
+
321
+ export function restoreExistingBrowser(resource) {
322
+ if (hasBrowserPaneHost() && !ensureBrowserPaneSource()) return 'pending';
323
+ if (!store.get('savedViewBrowserListReady')) { send('state_request'); return 'pending'; }
324
+ if (!store.get('connected') || !store.get('controlRuntimeId')) return 'pending';
325
+ ensurePanel();
326
+ var browser = (store.get('sharedBrowsers') || []).find(function (item) { return item.id === resource.id && item.phase !== 'ended'; });
327
+ var exact = browser && resource.runtimeId === store.get('controlRuntimeId') && resource.sessionOriginId === store.get('sessionOriginId');
328
+ claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
329
+ if (exact) { setBrowserTab(browser); send('view', { visible: true }); ui({ error: '' }); render(); return 'restored'; }
330
+ ui({ error: 'This saved browser is unavailable in the current conversation. No replacement browser was opened.' }); render();
331
+ return 'unavailable';
332
+ }
333
+
334
+ export function setBrowserExpanded(enabled) { ui({ fullscreen: !!enabled }); if (document.getElementById('shared-browser-panel')) render(); }
@@ -1,3 +1,4 @@
1
+ import { renderPaneRuntime } from './split-pane-runtime.js';
1
2
  import { store } from './store.js';
2
3
  import { getWs } from './ws-ref.js';
3
4
  import { copyToClipboard } from './utils.js';
@@ -295,6 +296,7 @@ export function createSplitPane(pane, closePane) {
295
296
  if (!vendorIcon.hidden) vendorIcon.src = VENDOR_AVATARS[vendor]; vendorIcon.alt = ""; vendorIcon.title = VENDOR_NAMES[vendor] || vendor; header.appendChild(vendorIcon);
296
297
  var title = document.createElement("span"); title.className = "split-pane-title"; title.textContent = pane.title; title.title = "Rename session";
297
298
  title.addEventListener("click", function () { if (title.getAttribute('aria-disabled') === 'true' || !syncPaneHeaderState(paneEl)) return; var current = sessionById(Number(paneEl.dataset.sessionId)); if (current) startPaneRename(header, title, { sessionId: current.id, title: current.title }); }); header.appendChild(title);
299
+ var dragSpace = document.createElement("span"); dragSpace.className = "split-pane-drag-space"; dragSpace.title = "Drag to rearrange panes"; dragSpace.setAttribute("aria-hidden", "true"); header.appendChild(dragSpace);
298
300
  var frame = document.createElement("iframe"); frame.className = "split-pane-frame"; frame.dataset.projectSlug = pane.slug;
299
301
  frame.dataset.sessionId = String(pane.sessionId); frame.src = paneUrl(pane); frame.title = pane.title;
300
302
  frame.dataset.sessionOriginId = session && session.sessionOriginId || '';
@@ -343,7 +345,7 @@ export function createSplitPane(pane, closePane) {
343
345
  }); header.appendChild(sidebarButton);
344
346
  var search = document.createElement("button"); search.type = "button"; search.className = "split-pane-search"; search.setAttribute("aria-label", "Search this session"); search.title = "Search this session"; search.innerHTML = iconHtml("search");
345
347
  header.insertBefore(search, close);
346
- frame.addEventListener('clay-pane-ready', function () {
348
+ function refreshReadyHeader() {
347
349
  if (paneEl.classList.contains('split-pane-cached')) return;
348
350
  var id = Number(frame.dataset.requestedSessionId || frame.dataset.sessionId), current = sessionById(id);
349
351
  var live = frame.clayPane && frame.clayPane.identity(), target = paneTarget(store.snap(), getCachedSessions(), id);
@@ -352,7 +354,9 @@ export function createSplitPane(pane, closePane) {
352
354
  frame.dataset.sessionOriginId = live.binding.sessionOriginId;
353
355
  updateSplitPaneMetadata(paneEl, { slug: frame.dataset.projectSlug, sessionId: id, title: current.title });
354
356
  syncPairChrome(paneEl.parentNode, store.get('splitPanes'));
355
- });
357
+ }
358
+ frame.addEventListener('clay-pane-ready', refreshReadyHeader);
359
+ frame.addEventListener('clay-pane-config', refreshReadyHeader);
356
360
  frame.addEventListener('clay-pane-activation-failed', function () {
357
361
  if (paneEl.classList.contains('split-pane-cached')) return;
358
362
  syncPaneHeaderState(paneEl);
@@ -453,12 +457,7 @@ export function updateSplitPaneMetadata(paneEl, pane) {
453
457
  var sidebarButton = paneEl.querySelector('.split-pane-sidebar');
454
458
  if (sidebarButton) sidebarButton.hidden = store.get('projectSplitViewSingletonOrigin') !== (session && session.sessionOriginId) && paneEl.dataset.active !== 'true';
455
459
  if (vendor) { var vendorName = session && session.vendor || "unknown"; vendor.hidden = !VENDOR_AVATARS[vendorName]; if (!vendor.hidden) vendor.src = VENDOR_AVATARS[vendorName]; vendor.title = VENDOR_NAMES[vendorName] || vendorName; }
456
- var runtime = paneEl.querySelector('.split-view-runtime');
457
- var runtimeName = session && session.vendor || "unknown", runtimeModelName = session && typeof session.model === "string" && session.model.trim() || "Model unavailable";
458
- var runtimeIcon = runtime.querySelector('.split-view-runtime-icon'), runtimeModel = runtime.querySelector('.split-view-model');
459
- if (runtimeIcon) { runtimeIcon.hidden = !VENDOR_AVATARS[runtimeName]; if (!runtimeIcon.hidden) runtimeIcon.src = VENDOR_AVATARS[runtimeName]; runtimeIcon.title = VENDOR_NAMES[runtimeName] || runtimeName; runtimeIcon.alt = ""; }
460
- if (runtimeModel) { runtimeModel.textContent = runtimeModelName; runtimeModel.title = (VENDOR_NAMES[runtimeName] || runtimeName) + " · " + runtimeModelName; }
461
- runtime.setAttribute("aria-label", (VENDOR_NAMES[runtimeName] || runtimeName) + " · " + runtimeModelName);
460
+ renderPaneRuntime(paneEl, session);
462
461
  renderPaneWorkers(paneEl, session);
463
462
  updatePaneFullAccessButton(paneEl.querySelector('.split-pane-full-access'), session);
464
463
  syncPaneHeaderState(paneEl);
@@ -0,0 +1,23 @@
1
+ import { store } from './store.js';
2
+ import { getCachedSessions } from './sidebar-sessions.js';
3
+ import { livePaneBinding, samePaneBinding, samePaneTarget, paneTarget } from './pane-binding.js';
4
+ import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
5
+ import { modelEntryValue } from './model-picker.js';
6
+
7
+ export function renderPaneRuntime(paneEl, session) {
8
+ var runtime = paneEl.querySelector('.split-view-runtime');
9
+ if (!runtime) return;
10
+ var vendor = session && session.vendor || 'unknown', model = modelEntryValue(session && session.model);
11
+ var frame = paneEl.querySelector('.split-pane-frame'), controller = frame && frame.clayPane;
12
+ var live = controller && controller.identity(), target = session && paneTarget(store.snap(), getCachedSessions(), session.id);
13
+ if (session && live && live.ready && live.sessionId === session.id && !frame.dataset.requestedSessionId &&
14
+ samePaneTarget(target, live.binding) && livePaneBinding(frame) && typeof controller.headerRuntime === 'function') {
15
+ var observed = controller.headerRuntime();
16
+ if (observed && observed.vendor === vendor && samePaneBinding(live.binding, observed.binding)) model = modelEntryValue(observed.model) || model;
17
+ }
18
+ var label = model || (vendor === 'unknown' ? 'Model unavailable' : 'Provider default');
19
+ var icon = runtime.querySelector('.split-view-runtime-icon'), text = runtime.querySelector('.split-view-model');
20
+ if (icon) { icon.hidden = !VENDOR_AVATARS[vendor]; if (!icon.hidden) icon.src = VENDOR_AVATARS[vendor]; icon.title = VENDOR_NAMES[vendor] || vendor; icon.alt = ''; }
21
+ if (text) { text.textContent = label; text.title = (VENDOR_NAMES[vendor] || vendor) + ' · ' + label; }
22
+ runtime.setAttribute('aria-label', (VENDOR_NAMES[vendor] || vendor) + ' · ' + label);
23
+ }