clay-server 5.2.1-beta.2 → 5.3.0-beta.2

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 (154) hide show
  1. package/lib/clay-control-presentation-binding.js +5 -3
  2. package/lib/clay-product-control-tools.js +3 -3
  3. package/lib/clay-product-control.js +4 -2
  4. package/lib/clay-screen-controls.js +18 -3
  5. package/lib/clay-split-view-awareness.js +35 -0
  6. package/lib/clay-split-view-controls.js +122 -0
  7. package/lib/clay-split-view-input.js +20 -0
  8. package/lib/clay-split-view-render-proof.js +22 -0
  9. package/lib/pane-asset-manifest.js +63 -0
  10. package/lib/project-connection.js +19 -10
  11. package/lib/project-session-actions.js +1 -0
  12. package/lib/project-session-delete.js +2 -2
  13. package/lib/project-session-folders.js +1 -0
  14. package/lib/project-sessions.js +6 -1
  15. package/lib/project-split-view-document.js +33 -0
  16. package/lib/project-split-view-names.js +67 -0
  17. package/lib/project-split-view-organization.js +23 -0
  18. package/lib/project-split-views.js +479 -0
  19. package/lib/project-worker-conversation.js +222 -0
  20. package/lib/project-worker-creation-proposal.js +3 -4
  21. package/lib/project-worker-proposal.js +1 -1
  22. package/lib/project-worker-removal.js +101 -0
  23. package/lib/project.js +28 -1
  24. package/lib/public/app.js +32 -13
  25. package/lib/public/css/base.css +9 -0
  26. package/lib/public/css/filebrowser.css +0 -1
  27. package/lib/public/css/pane-header-controls.css +100 -0
  28. package/lib/public/css/pane.css +24 -41
  29. package/lib/public/css/popup-chat.css +123 -0
  30. package/lib/public/css/right-workbench.css +50 -18
  31. package/lib/public/css/session-folders.css +1 -1
  32. package/lib/public/css/split-view-layout.css +463 -0
  33. package/lib/public/css/split-view-worker-activity.css +97 -0
  34. package/lib/public/css/sticky-notes.css +13 -0
  35. package/lib/public/index.html +3 -3
  36. package/lib/public/modules/app-connection.js +4 -1
  37. package/lib/public/modules/app-favicon.js +1 -1
  38. package/lib/public/modules/app-messages.js +31 -9
  39. package/lib/public/modules/chat-render-runtime.js +54 -0
  40. package/lib/public/modules/clay-control-copy.js +3 -0
  41. package/lib/public/modules/clay-navigation-render.js +1 -1
  42. package/lib/public/modules/clay-owner-navigation.js +15 -5
  43. package/lib/public/modules/clay-split-view-screen.js +74 -0
  44. package/lib/public/modules/clay-tool-activity.js +5 -0
  45. package/lib/public/modules/command-palette.js +30 -28
  46. package/lib/public/modules/contextual-clay-session.js +26 -2
  47. package/lib/public/modules/contextual-clay.js +5 -2
  48. package/lib/public/modules/contextual-mate-sessions.js +2 -0
  49. package/lib/public/modules/filebrowser.js +6 -2
  50. package/lib/public/modules/generation-stop.js +7 -1
  51. package/lib/public/modules/knowledge-workbench-entry.js +43 -0
  52. package/lib/public/modules/markdown-view-mode.js +34 -0
  53. package/lib/public/modules/markdown.js +11 -0
  54. package/lib/public/modules/mate-knowledge-workbench.js +2 -7
  55. package/lib/public/modules/mate-sidebar.js +1 -1
  56. package/lib/public/modules/palette-clay-conversation.js +87 -0
  57. package/lib/public/modules/palette-render-helpers.js +25 -0
  58. package/lib/public/modules/pane-activation.js +46 -7
  59. package/lib/public/modules/pane-binding.js +12 -2
  60. package/lib/public/modules/pane-bridge.js +12 -3
  61. package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
  62. package/lib/public/modules/pane-feature-assets.js +35 -0
  63. package/lib/public/modules/pane-file-bridge.js +8 -3
  64. package/lib/public/modules/pane-search-receipt.js +12 -0
  65. package/lib/public/modules/permission-control.js +6 -6
  66. package/lib/public/modules/popup-chat-rendering.js +244 -0
  67. package/lib/public/modules/project-activation.js +2 -2
  68. package/lib/public/modules/qrcode.js +12 -1
  69. package/lib/public/modules/right-workbench.js +68 -0
  70. package/lib/public/modules/search-hints.js +79 -0
  71. package/lib/public/modules/session-context-menu.js +35 -2
  72. package/lib/public/modules/session-folder-collapse.js +2 -1
  73. package/lib/public/modules/session-folder-context.js +24 -13
  74. package/lib/public/modules/session-folder-delete.js +18 -1
  75. package/lib/public/modules/session-folder-dnd.js +4 -1
  76. package/lib/public/modules/session-folder-layout.js +7 -6
  77. package/lib/public/modules/session-search-pane-state.js +30 -0
  78. package/lib/public/modules/session-search.js +53 -13
  79. package/lib/public/modules/sidebar-mobile.js +3 -3
  80. package/lib/public/modules/sidebar-projects.js +1 -0
  81. package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
  82. package/lib/public/modules/sidebar-sessions.js +57 -24
  83. package/lib/public/modules/sidebar.js +20 -3
  84. package/lib/public/modules/split-group-helpers.js +1 -1
  85. package/lib/public/modules/split-group-restore.js +8 -11
  86. package/lib/public/modules/split-pair-ui.js +18 -0
  87. package/lib/public/modules/split-pane-header-state.js +45 -0
  88. package/lib/public/modules/split-pane-reconciler.js +23 -4
  89. package/lib/public/modules/split-pane-renderer.js +330 -23
  90. package/lib/public/modules/split-pane-retention.js +157 -0
  91. package/lib/public/modules/split-session-boundary.js +21 -0
  92. package/lib/public/modules/split-view-create-action.js +15 -0
  93. package/lib/public/modules/split-view-create-menu.js +83 -0
  94. package/lib/public/modules/split-view-create-request.js +23 -0
  95. package/lib/public/modules/split-view-layout-client.js +198 -0
  96. package/lib/public/modules/split-view-layout-icons.js +37 -0
  97. package/lib/public/modules/split-view-layout-scope.js +38 -0
  98. package/lib/public/modules/split-view-organization.js +73 -0
  99. package/lib/public/modules/split-view-presentation-close.js +35 -0
  100. package/lib/public/modules/split-view-presentation-drag.js +278 -0
  101. package/lib/public/modules/split-view-presentation-drop.js +57 -0
  102. package/lib/public/modules/split-view-presentation-lookup.js +44 -0
  103. package/lib/public/modules/split-view-presentation-renderer.js +494 -0
  104. package/lib/public/modules/split-view-presentations.js +498 -0
  105. package/lib/public/modules/split-view-shell-controls.js +28 -0
  106. package/lib/public/modules/split-view-sidebar-menu.js +42 -0
  107. package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
  108. package/lib/public/modules/split-view-singleton.js +21 -0
  109. package/lib/public/modules/split-view-worker-activity.js +94 -0
  110. package/lib/public/modules/split-view-worker-controls.js +30 -0
  111. package/lib/public/modules/split-view-worker-removal.js +137 -0
  112. package/lib/public/modules/split-view-worker-scroll.js +57 -0
  113. package/lib/public/modules/split-view-worker-transcript.js +46 -0
  114. package/lib/public/modules/split-view-workers.js +418 -0
  115. package/lib/public/modules/split-view.js +61 -22
  116. package/lib/public/modules/sticky-notes.js +9 -2
  117. package/lib/public/modules/tools.js +25 -5
  118. package/lib/public/modules/workbench-layout-preference.js +76 -0
  119. package/lib/public/modules/worker-created-presentation.js +37 -0
  120. package/lib/public/pane-assets/app-2DRBYY3L.js +1 -0
  121. package/lib/public/pane-assets/chunk-4BM7UVNG.js +2 -0
  122. package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
  123. package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
  124. package/lib/public/pane-assets/chunk-C4RVVYNT.js +1 -0
  125. package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
  126. package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
  127. package/lib/public/pane-assets/chunk-O6MQL6MR.js +167 -0
  128. package/lib/public/pane-assets/chunk-PO2JOHHW.js +1 -0
  129. package/lib/public/pane-assets/chunk-UMGHINOT.js +1 -0
  130. package/lib/public/pane-assets/manifest.json +553 -0
  131. package/lib/public/pane-assets/style-63EZCREE.css +1 -0
  132. package/lib/public/style.css +5 -1
  133. package/lib/server-owner-ui.js +6 -2
  134. package/lib/server-owner-view-observation.js +34 -0
  135. package/lib/server-palette.js +1 -0
  136. package/lib/server-pane-bootstrap.js +24 -0
  137. package/lib/server-settings.js +2 -0
  138. package/lib/server-static-assets.js +52 -0
  139. package/lib/server-workbench-layout-settings.js +56 -0
  140. package/lib/server.js +6 -45
  141. package/lib/session-activity-state.js +28 -0
  142. package/lib/session-list-delivery.js +17 -0
  143. package/lib/session-pair-turn-control.js +1 -6
  144. package/lib/session-search.js +3 -0
  145. package/lib/session-split-group-anchors.js +2 -2
  146. package/lib/session-split-group-roles.js +5 -8
  147. package/lib/session-split-group-v2-store.js +0 -3
  148. package/lib/sessions.js +16 -6
  149. package/lib/split-view-layout.js +132 -0
  150. package/lib/users-workbench-layout-preferences.js +33 -0
  151. package/lib/users.js +3 -0
  152. package/lib/workspace-query-service.js +1 -5
  153. package/lib/ws-schema.js +16 -0
  154. package/package.json +6 -3
@@ -1,4 +1,5 @@
1
1
  // Saved conversations are independent of which compact renderers are mounted.
2
+ import { handlePaletteClayConversation } from './palette-clay-conversation.js';
2
3
  import { store } from './store.js';
3
4
  import { getWs } from './ws-ref.js';
4
5
  import { contextualMate, contextualMateStamp, contextualFrame } from './contextual-mate-identity.js';
@@ -111,6 +112,7 @@ export function attachContextualConversations(surface) {
111
112
  refreshIcons(); render();
112
113
  }
113
114
  export function handleContextualConversations(msg) {
115
+ if (handlePaletteClayConversation(msg)) return true;
114
116
  if (msg.type !== 'contextual_mate_sessions_result') return false;
115
117
  var kind = requestState('list') && requestState('list').requestId === msg.requestId ? 'list' : 'action';
116
118
  var request = requestState(kind);
@@ -16,6 +16,7 @@ import { initFileBrowserContextMenu, downloadProjectFile } from './filebrowser-c
16
16
  import { registerClayFileLinkOpener } from './clay-file-links.js';
17
17
  import { isPlantUmlFile, renderPlantUmlPreview, cancelPlantUmlPreview } from './plantuml-preview.js';
18
18
  import { isHtmlFile, renderHtmlPreview } from './html-preview.js';
19
+ import { initialMarkdownRendered, rememberMarkdownViewMode, resetMarkdownViewModes } from './markdown-view-mode.js';
19
20
 
20
21
  var ctx;
21
22
  var showDropHint = function () {};
@@ -48,7 +49,7 @@ export function initFileBrowser(_ctx) {
48
49
  ctx.fileViewerEl.classList.remove("hidden");
49
50
  if (path !== currentFilePath || (store.get("fileViewerIsHtml") && isRendered && !document.querySelector(".file-viewer-html-preview"))) requestFileContent(path);
50
51
  },
51
- onEmpty: function() { teardownFileViewer(); },
52
+ onEmpty: function() { resetMarkdownViewModes(); teardownFileViewer(); },
52
53
  });
53
54
  registerRightWorkbench("file-viewer", teardownFileViewer);
54
55
 
@@ -204,6 +205,7 @@ export function initFileBrowser(_ctx) {
204
205
  document.getElementById("file-viewer-render").addEventListener("click", function () {
205
206
  if (currentContent == null || (!currentIsMarkdown && !currentIsSvg && !store.get("fileViewerIsPlantUml") && !store.get("fileViewerIsHtml"))) return;
206
207
  isRendered = !isRendered;
208
+ if (currentIsMarkdown) rememberMarkdownViewMode((store.get('clayControlFileView') || {}).projectSlug, currentFilePath, isRendered);
207
209
  if (store.get("fileViewerIsHtml")) {
208
210
  renderHtmlBody();
209
211
  return;
@@ -534,6 +536,7 @@ export function setFileViewerFullscreen(enabled) {
534
536
  export function resetFileBrowser() {
535
537
  releaseRightWorkbench("file-viewer");
536
538
  clearFileViewerTabs();
539
+ resetMarkdownViewModes();
537
540
  teardownFileViewer();
538
541
  // Clear all cached state
539
542
  treeData = {};
@@ -1217,6 +1220,7 @@ function showFileContent(msg, expectedRevision) {
1217
1220
  store.set({ fileViewerIsHtml: !lightweightPreview && isHtmlFile(msg.path) });
1218
1221
  store.set({ fileViewerIsPlantUml: !lightweightPreview && isPlantUmlFile(msg.path) });
1219
1222
  if (lightweightPreview) isRendered = false;
1223
+ if (currentIsMarkdown && !keepRenderState) isRendered = initialMarkdownRendered((store.get('clayControlFileView') || {}).projectSlug, msg.path, store.get('pendingFileNavigation'), pendingOpenMode);
1220
1224
  if (pendingRenderedOpen && currentIsMarkdown) isRendered = true;
1221
1225
 
1222
1226
  if (currentIsMarkdown || currentIsSvg || store.get("fileViewerIsPlantUml") || store.get("fileViewerIsHtml")) {
@@ -1227,7 +1231,7 @@ function showFileContent(msg, expectedRevision) {
1227
1231
  copyBtn.title = "Copy contents";
1228
1232
  }
1229
1233
 
1230
- // Markdown starts as source; HTML, SVG and diagrams start as previews.
1234
+ // Markdown, HTML, SVG and diagrams start as previews; a line target or diff starts as source.
1231
1235
  if (lightweightPreview) {
1232
1236
  renderLargeTextFile(bodyEl, msg.content, msg.size);
1233
1237
  } else if (currentIsMarkdown) {
@@ -5,7 +5,13 @@ import { isDriverOperatedView } from './worker-pane-lock.js';
5
5
  export function stopGeneration() {
6
6
  var ws = getWs();
7
7
  if (!store.get('processing') || !store.get('connected') || !ws || ws.readyState !== 1) return false;
8
- ws.send(JSON.stringify({ type: 'stop' }));
8
+ var message = { type: 'stop' };
9
+ if (store.get('paneMode')) {
10
+ var binding = store.get('paneCommittedBinding');
11
+ if (!store.get('paneReady') || !binding || binding.sessionId !== store.get('activeSessionId') || binding.sessionOriginId !== store.get('sessionOriginId')) return false;
12
+ message.paneBinding = binding;
13
+ }
14
+ ws.send(JSON.stringify(message));
9
15
  return true;
10
16
  }
11
17
 
@@ -0,0 +1,43 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { refreshIcons } from './icons.js';
4
+ import { showToast } from './utils.js';
5
+ import { ensurePaneFeatureAssets } from './pane-feature-assets.js';
6
+
7
+ var knowledgeWorkbenchModule = null;
8
+
9
+ export function ensureKnowledgeButton() {
10
+ var button = document.getElementById('knowledge-workbench-btn'); if (button) return button;
11
+ var tools = document.getElementById('session-actions'); if (!tools) return null;
12
+ button = document.createElement('button'); button.id = 'knowledge-workbench-btn'; button.type = 'button'; button.className = 'palette-tile'; button.title = 'Knowledge'; button.setAttribute('aria-label', 'Knowledge'); button.innerHTML = '<i data-lucide="book-open"></i><span class="tool-btn-label">Knowledge</span>';
13
+ var fixed = tools.querySelector('[data-fixed="true"]');
14
+ tools.insertBefore(button, fixed ? fixed.nextSibling : tools.firstChild); refreshIcons(button); return button;
15
+ }
16
+ function sameContext(receipt) {
17
+ var state = store.snap();
18
+ return receipt.owner === state.myUserId && receipt.slug === state.currentSlug && receipt.socket === getWs() &&
19
+ receipt.generation === state.projectSplitViewSocketGeneration && receipt.binding === state.paneCommittedBinding && state.connected;
20
+ }
21
+ export function initMateKnowledgeWorkbench() {
22
+ var button = ensureKnowledgeButton(); if (!button || store.get('knowledgeWorkbenchEntryInitialized')) return;
23
+ store.set({ knowledgeWorkbenchEntryInitialized: true });
24
+ button.addEventListener('click', open);
25
+ function open(event) {
26
+ event.stopImmediatePropagation();
27
+ if (button.disabled) return;
28
+ var state = store.snap(), receipt = { owner: state.myUserId, slug: state.currentSlug, socket: getWs(), generation: state.projectSplitViewSocketGeneration, binding: state.paneCommittedBinding };
29
+ button.disabled = true;
30
+ Promise.all([import('./mate-knowledge-workbench.js'), ensurePaneFeatureAssets('editor')]).then(function (loaded) {
31
+ var module = loaded[0];
32
+ if (!knowledgeWorkbenchModule) {
33
+ module.initMateKnowledgeWorkbench(); knowledgeWorkbenchModule = module;
34
+ button.removeEventListener('click', open);
35
+ }
36
+ if (sameContext(receipt)) module.openKnowledgeWorkbench();
37
+ }).catch(function (error) { if (sameContext(receipt)) showToast(error.message, 'error'); }).finally(function () { button.disabled = false; });
38
+ }
39
+ }
40
+ export function handleKnowledgeWorkbenchMessage(message) {
41
+ var module = knowledgeWorkbenchModule;
42
+ return module ? module.handleKnowledgeWorkbenchMessage(message) : false;
43
+ }
@@ -0,0 +1,34 @@
1
+ // Initial Markdown view for the file viewer. Rendered is the default; an
2
+ // explicit line target or diff needs source rows, and a toggle made by the
3
+ // user for a path in this tab session is respected on later focus/reopen.
4
+ // Choices are scoped to the source project that owns the file.
5
+
6
+ import { store } from './store.js';
7
+
8
+ function modeKey(projectSlug, path) {
9
+ return JSON.stringify([projectSlug || null, path]);
10
+ }
11
+
12
+ function chosenMode(projectSlug, path) {
13
+ var modes = store.get('fileViewerMarkdownModes') || {};
14
+ var key = modeKey(projectSlug, path);
15
+ return Object.prototype.hasOwnProperty.call(modes, key) ? modes[key] : null;
16
+ }
17
+
18
+ export function initialMarkdownRendered(projectSlug, path, pendingNavigation, pendingMode) {
19
+ if (pendingNavigation && pendingNavigation.line && pendingNavigation.path === path) return false;
20
+ if (pendingMode && pendingMode.type === 'diff') return false;
21
+ var chosen = chosenMode(projectSlug, path);
22
+ return chosen ? chosen === 'rendered' : true;
23
+ }
24
+
25
+ export function rememberMarkdownViewMode(projectSlug, path, rendered) {
26
+ if (!path) return;
27
+ var next = Object.assign({}, store.get('fileViewerMarkdownModes') || {});
28
+ next[modeKey(projectSlug, path)] = rendered ? 'rendered' : 'source';
29
+ store.set({ fileViewerMarkdownModes: next });
30
+ }
31
+
32
+ export function resetMarkdownViewModes() {
33
+ store.set({ fileViewerMarkdownModes: {} });
34
+ }
@@ -1,3 +1,4 @@
1
+ import { ensurePaneFeatureAssets } from './pane-feature-assets.js';
1
2
  import { enhanceSessionCitations } from './clay-session-links.js';
2
3
  import { renderBrowserLink, enhanceBrowserLinks } from './clay-browser-links.js';
3
4
  import { renderWireframeBlocks } from './wireframe-chat.js';
@@ -41,6 +42,7 @@ marked.use({
41
42
  var mermaidInitialized = false;
42
43
 
43
44
  export function updateMermaidTheme(vars) {
45
+ if (typeof mermaid === "undefined") return;
44
46
  mermaidInitialized = true;
45
47
  mermaid.initialize({
46
48
  startOnLoad: false,
@@ -140,6 +142,15 @@ export function renderMermaidBlocks(el) {
140
142
  if (store.get('replayingHistory')) return;
141
143
  renderWireframeBlocks(el);
142
144
  var blocks = el.querySelectorAll("pre code.language-mermaid");
145
+ if (blocks.length && typeof mermaid === 'undefined') {
146
+ var binding = store.get('panePendingBinding') || store.get('paneCommittedBinding'), origin = store.get('sessionOriginId');
147
+ var owner = store.get('myUserId'), slug = store.get('currentSlug'), generation = store.get('projectSplitViewSocketGeneration');
148
+ ensurePaneFeatureAssets('mermaid').then(function () {
149
+ if (el.isConnected && binding === (store.get('panePendingBinding') || store.get('paneCommittedBinding')) && origin === store.get('sessionOriginId') &&
150
+ owner === store.get('myUserId') && slug === store.get('currentSlug') && generation === store.get('projectSplitViewSocketGeneration')) renderMermaidBlocks(el);
151
+ }).catch(function () { /* Preserve readable source when diagram assets fail. */ });
152
+ return;
153
+ }
143
154
  blocks.forEach(function (codeEl) {
144
155
  var pre = codeEl.parentElement;
145
156
  if (!pre || pre.dataset.mermaidProcessed) return;
@@ -1,3 +1,4 @@
1
+ import { ensureKnowledgeButton as ensureButton } from './knowledge-workbench-entry.js';
1
2
  import { store } from './store.js';
2
3
  import { getWs } from './ws-ref.js';
3
4
  import { iconHtml, refreshIcons } from './icons.js';
@@ -93,13 +94,7 @@ function ensurePanel() {
93
94
  var newButton = panel.querySelector('[data-knowledge-new]'); newButton.insertAdjacentHTML('beforebegin', '<button type="button" data-knowledge-back title="Back" aria-label="Previous document">' + iconHtml('arrow-left') + '</button><button type="button" data-knowledge-forward title="Forward" aria-label="Next document">' + iconHtml('arrow-right') + '</button>');
94
95
  host.appendChild(panel); bindPanel(); refreshIcons(panel);
95
96
  }
96
- function ensureButton() {
97
- var button = document.getElementById('knowledge-workbench-btn'); if (button) return button;
98
- var tools = document.getElementById('session-actions'); if (!tools) return null;
99
- button = document.createElement('button'); button.id = 'knowledge-workbench-btn'; button.type = 'button'; button.className = 'palette-tile'; button.title = 'Knowledge'; button.setAttribute('aria-label', 'Knowledge'); button.innerHTML = '<i data-lucide="book-open"></i><span class="tool-btn-label">Knowledge</span>';
100
- var fixed = tools.querySelector('[data-fixed="true"]');
101
- tools.insertBefore(button, fixed ? fixed.nextSibling : tools.firstChild); refreshIcons(button); return button;
102
- }
97
+
103
98
  function bindPanel() {
104
99
  panel.querySelector('[data-knowledge-close]').addEventListener('click', closeKnowledgeWorkbench);
105
100
  panel.querySelector('[data-knowledge-new]').addEventListener('click', function () { showKnowledgeCreateMenu(panel); });
@@ -406,7 +406,7 @@ function appendMateSessionTitleLine(el, textSpan) {
406
406
 
407
407
  export function renderMateSessionList(sessions) {
408
408
  if (sessions) cachedSessions = sessions;
409
- if (!listEl) return;
409
+ if (!listEl || (store.get('paneMode') && !store.get('activeProjectMateId') && !store.get('dmMode') && !store.get('paneActivationUnsupported') && window.frameElement && window.frameElement.clayPane && !new URLSearchParams(location.search).has('contextual'))) return;
410
410
  listEl.innerHTML = "";
411
411
 
412
412
  if (cachedSessions.length === 0) {
@@ -0,0 +1,87 @@
1
+ // Palette selection resolves one existing private conversation before presentation.
2
+ import { store } from './store.js';
3
+ import { getWs } from './ws-ref.js';
4
+ import { contextualMate, contextualMateStamp, contextualFrame, contextualTrigger } from './contextual-mate-identity.js';
5
+ import { selectContextualConversation, enterComposer } from './contextual-clay.js';
6
+ import { pauseContextualSearchForSelection } from './contextual-search.js';
7
+ import { closeCommandPalette } from './command-palette.js';
8
+ import { showToast } from './utils.js';
9
+
10
+ var socket = null, timer = null, surface = null;
11
+ function current() { return store.get('paletteClayConversation'); }
12
+ function parentView() { return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('projectSplitViewActiveId')]); }
13
+ function selection() { return JSON.stringify(store.get('contextualClay') || null); }
14
+ function warn(message) { showToast(message || 'This conversation is unavailable. Refresh search and try again.', 'warn'); }
15
+ function clear() {
16
+ clearTimeout(timer); timer = null; socket = null;
17
+ if (surface) { surface.removeEventListener('contextual-clay-ready', ready); surface.removeEventListener('contextual-ask-stopped', stopped); }
18
+ surface = null; store.set({ paletteClayConversation: null });
19
+ }
20
+ function stopped() { if (current()) { clear(); warn('Opening was stopped. Select the conversation again to resume.'); } }
21
+ function live(request) {
22
+ var mate = contextualMate(request.mateId);
23
+ return store.get('connected') && socket === getWs() && socket && socket.readyState === 1 && request.ownerId === store.get('myUserId') && mate && !mate.lifecycleDeletion && mate.builtinKey === 'clay' && mate.createdBy === request.ownerId && contextualMateStamp(mate) === request.stamp && parentView() === request.view;
24
+ }
25
+ function guard() {
26
+ var action = store.get('contextualConversationRequest'), popup = store.get('contextualClay') || {};
27
+ if (action && action.status === 'loading' || popup.startingAsk || popup.mode === 'opening') { warn('Wait for the current conversation request to finish.'); return false; }
28
+ try { var frame = contextualFrame(); if (frame && frame.conversation) frame.conversation.checkDraft(); return true; }
29
+ catch (error) { warn(error.message); return false; }
30
+ }
31
+ export function openPaletteClayConversation(item, query, entryIndex) {
32
+ var mateId = item.mateId || String(item.projectSlug || '').slice(5);
33
+ var mate = contextualMate(mateId), owner = store.get('myUserId');
34
+ if (!mate || mate.lifecycleDeletion || item.projectSlug !== 'mate-' + mateId) { warn(); return true; }
35
+ if (mate.builtinKey !== 'clay') return false;
36
+ if (!owner || mate.createdBy !== owner || !Number.isSafeInteger(item.sessionId) || item.sessionId <= 0 || typeof item.sessionOriginId !== 'string' || !item.sessionOriginId) { warn(); return true; }
37
+ if (current()) { warn('Wait for the current conversation request to finish.'); return true; }
38
+ if (!guard()) return true;
39
+ socket = getWs();
40
+ if (!store.get('connected') || !socket || socket.readyState !== 1) { socket = null; warn('Reconnect before opening this conversation.'); return true; }
41
+ var request = { requestId: 'mate-conversations-' + crypto.randomUUID(), phase: 'resolving', mateId: mate.id, stamp: contextualMateStamp(mate), ownerId: owner,
42
+ sessionId: item.sessionId, sessionOriginId: item.sessionOriginId, view: parentView(), selection: selection(), query: query || null,
43
+ entryIndex: entryIndex != null && Number.isSafeInteger(Number(entryIndex)) && Number(entryIndex) >= 0 ? Number(entryIndex) : null };
44
+ store.set({ paletteClayConversation: request });
45
+ surface = document.getElementById('contextual-clay');
46
+ if (surface) surface.addEventListener('contextual-ask-stopped', stopped);
47
+ timer = setTimeout(function () { if (current() && current().requestId === request.requestId) { clear(); warn('The conversation did not become ready. Select it again to retry.'); } }, 15000);
48
+ try { socket.send(JSON.stringify({ type: 'home_mate_session_open', presentation: 'contextual', requestId: request.requestId, mateId: request.mateId, mateStamp: request.stamp, sessionId: 'local:' + request.sessionId, sessionOriginId: request.sessionOriginId })); }
49
+ catch (error) { clear(); warn('The connection changed. Select the conversation again after reconnecting.'); }
50
+ return true;
51
+ }
52
+ export function handlePaletteClayConversation(msg) {
53
+ var request = current();
54
+ if (msg.type !== 'contextual_mate_sessions_result' || !request || msg.requestId !== request.requestId) return false;
55
+ if (request.phase !== 'resolving') return true;
56
+ if (!live(request) || request.selection !== selection() || msg.mateId !== request.mateId || msg.mateStamp !== request.stamp || !msg.ok || msg.sessionId !== request.sessionId || msg.sessionOriginId !== request.sessionOriginId) { clear(); warn(); return true; }
57
+ if (!guard()) { clear(); return true; }
58
+ pauseContextualSearchForSelection();
59
+ store.set({ paletteClayConversation: Object.assign({}, request, { phase: 'selecting' }) });
60
+ var selected = false;
61
+ try { selected = selectContextualConversation(contextualMate(request.mateId), request.sessionId, request.sessionOriginId); } catch (error) { /* An unavailable child must not fall back to native navigation. */ }
62
+ if (!selected || !current() || !live(request)) { clear(); warn('This conversation could not be opened. Your draft is retained.'); return true; }
63
+ store.set({ paletteClayConversation: Object.assign({}, request, { phase: 'opening', selection: selection() }) });
64
+ closeCommandPalette();
65
+ if (surface) surface.removeEventListener('contextual-ask-stopped', stopped);
66
+ surface = document.getElementById('contextual-clay');
67
+ if (surface) { var trigger = contextualTrigger(request.mateId); if (trigger) { surface.dataset.focusEntry = 'true'; trigger.focus({ preventScroll: true }); enterComposer(); } surface.addEventListener('contextual-clay-ready', ready); surface.addEventListener('contextual-ask-stopped', stopped); }
68
+ ready();
69
+ return true;
70
+ }
71
+ function ready() {
72
+ var request = current();
73
+ if (!request || request.phase !== 'opening') return;
74
+ if (!live(request) || request.selection !== selection()) { clear(); warn('Your view changed. Select the conversation again.'); return; }
75
+ var frame = contextualFrame(request.mateId, request.sessionId), controller = frame && frame.conversation;
76
+ if (!controller || controller.source !== frame.contentWindow || frame.hidden || frame.inert) return;
77
+ var identity = controller.identity();
78
+ if (!identity.ready) return;
79
+ if (identity.ownerId !== request.ownerId || identity.mateId !== request.mateId || identity.sessionId !== request.sessionId || identity.sessionOriginId !== request.sessionOriginId || !identity.connected || identity.available === false) { clear(); warn(); return; }
80
+ if ((request.query || request.entryIndex !== null) && (!controller.navigateEntry || !controller.navigateEntry(request))) { clear(); warn('The conversation opened, but the requested passage could not be located.'); return; }
81
+ clear();
82
+ }
83
+ store.subscribe(function () {
84
+ var request = current();
85
+ if (!request && socket) { clear(); return; }
86
+ if (request && (!live(request) || request.phase !== 'selecting' && request.selection !== selection())) { clear(); warn('Your view or connection changed. Select the conversation again.'); }
87
+ });
@@ -0,0 +1,25 @@
1
+ export function formatRelativeDate(ts) {
2
+ if (!ts) return "";
3
+ var diff = Math.max(0, Date.now() - ts);
4
+ var min = Math.floor(diff / 60000);
5
+ if (min < 1) return "just now";
6
+ if (min < 60) return min + "m ago";
7
+ var hr = Math.floor(min / 60);
8
+ if (hr < 24) return hr + "h ago";
9
+ var days = Math.floor(hr / 24);
10
+ if (days < 7) return days + "d ago";
11
+ if (days < 30) return Math.floor(days / 7) + "w ago";
12
+ var d = new Date(ts);
13
+ var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
14
+ return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
15
+ }
16
+
17
+
18
+ export function paletteSelectionIndex(previous, items, query) {
19
+ var index = previous ? items.findIndex(function (item) {
20
+ if (item.type !== previous.type || item.query !== previous.query) return false;
21
+ if (!item.data || !previous.data) return !item.data && !previous.data;
22
+ return item.data.projectSlug === previous.data.projectSlug && item.data.sessionId === previous.data.sessionId && item.data.sessionOriginId === previous.data.sessionOriginId;
23
+ }) : -1;
24
+ return index >= 0 ? index : query ? 0 : -1;
25
+ }
@@ -1,3 +1,4 @@
1
+ import { disarmStickyBottom } from './chat-render-runtime.js';
1
2
  import { store } from './store.js';
2
3
  import { getWs } from './ws-ref.js';
3
4
  import { getCachedSessions } from './sidebar-sessions.js';
@@ -15,34 +16,58 @@ function block(value) {
15
16
  document.documentElement.dataset.paneReady = value ? 'false' : 'true';
16
17
  }
17
18
  function draftKey() { return String(store.get('myUserId')) + ':' + store.get('sessionOriginId'); }
19
+ function rememberPanePosition() {
20
+ var messages = document.getElementById('messages'), key = draftKey();
21
+ if (!messages || !store.get('sessionOriginId') || store.get('panePendingBinding')) return;
22
+ var positions = Object.assign({}, store.get('paneRetainedPositions') || {});
23
+ delete positions[key];
24
+ positions[key] = { top: messages.scrollTop, height: messages.scrollHeight, viewport: messages.clientHeight };
25
+ var keys = Object.keys(positions);
26
+ while (keys.length > 8) delete positions[keys.shift()];
27
+ store.set({ paneRetainedPositions: positions });
28
+ }
29
+ function suspendPane() {
30
+ if (!store.get('paneSuspended')) rememberPanePosition();
31
+ clearDeadline();
32
+ store.set({ paneSuspended: true, paneReady: false, panePendingBinding: null });
33
+ block(true);
34
+ }
18
35
  function clearDeadline() {
19
36
  if (store.get('paneActivationTimer')) clearTimeout(store.get('paneActivationTimer'));
20
37
  store.set({ paneActivationTimer: null });
21
38
  }
22
- function failed(requestId) {
39
+ function failed(requestId, reason) {
23
40
  var pending = store.get('panePendingBinding');
24
41
  if (!pending || requestId !== pending.requestId) return;
25
42
  clearDeadline();
26
43
  store.set({ paneReady: false, panePendingBinding: null });
27
44
  block(true);
28
45
  var frame = window.frameElement;
29
- if (frame) frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-activation-failed'));
46
+ if (frame) {
47
+ if (reason) frame.dataset.paneActivationFailure = reason;
48
+ frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-activation-failed'));
49
+ }
30
50
  }
31
51
 
32
52
  // Claim ordinary initial pins before the legacy session-list handler runs.
33
53
  // Authentication, list and group metadata can arrive in either order; the
34
54
  // subscription retries when those authoritative prerequisites become ready.
35
55
  export function primePaneSession() {
36
- if (!enabled() || !window.frameElement || !window.frameElement.clayPane) return false;
37
- var id = store.get('paneSessionId');
56
+ if (store.get('paneSuspended') || !enabled() || !window.frameElement || !window.frameElement.clayPane) return false;
57
+ var frame = window.frameElement;
58
+ var id = Number(frame.dataset.requestedSessionId || frame.dataset.sessionId || store.get('paneSessionId'));
59
+ if (!Number.isSafeInteger(id) || id < 1) return false;
38
60
  var session = (getCachedSessions() || []).find(function (item) { return item.id === id; });
39
61
  if (!session || session.isMate || (session.runtimeMode || session.mode || 'gui') !== 'gui' || session.tuiSuspended) return false;
40
- activatePaneSession(id);
62
+ var target = paneTarget(store.snap(), getCachedSessions(), id);
63
+ var existing = store.get('panePendingBinding') || store.get('paneCommittedBinding');
64
+ activatePaneSession(id, store.get('paneSessionId') !== id || !samePaneTarget(existing, target), target);
41
65
  return true;
42
66
  }
43
67
 
44
68
  export function activatePaneSession(id, force, expectedTarget) {
45
69
  var state = store.snap(), ws = getWs();
70
+ if (window.frameElement && window.frameElement.closest && window.frameElement.closest('.split-pane-cached')) return false;
46
71
  if (state.authIdentityLoaded === false) return false;
47
72
  if (state.isMultiUserMode && !state.myUserId) return false;
48
73
  if (!enabled() || !state.connected || !ws || ws.readyState !== 1 || state.splitGroupsProjectSlug !== state.currentSlug) return false;
@@ -54,6 +79,7 @@ export function activatePaneSession(id, force, expectedTarget) {
54
79
  var old = state.paneCommittedBinding, pending = state.panePendingBinding;
55
80
  if (!force && !state.paneSuspended && samePaneTarget(pending || old, target)) return true;
56
81
  if (!pending && state.sessionOriginId) {
82
+ if (!state.paneSuspended) rememberPanePosition();
57
83
  var draft;
58
84
  try { draft = takeComposerDraft(); } catch (error) { return false; }
59
85
  var drafts = Object.assign({}, state.paneRichDrafts || {});
@@ -67,9 +93,11 @@ export function activatePaneSession(id, force, expectedTarget) {
67
93
  var path = new URL(store.get('wsPath'), location.origin);
68
94
  path.searchParams.set('session', String(id));
69
95
  store.set({ paneActivationGeneration: generation, panePendingBinding: target, paneReady: false, paneSuspended: false,
96
+ paneLayoutScrollLock: null, paneLastUserScrollTop: null, paneUserScrollIntent: null, paneUserScrollRevision: 0,
70
97
  paneSessionId: id, panePinPending: false, wsPath: path.pathname + path.search });
71
98
  block(true);
72
99
  store.set({ paneActivationTimer: setTimeout(function () { failed(target.requestId); }, 12000) });
100
+ if (typeof performance !== 'undefined' && performance.mark) { performance.clearMarks('clay:activation-request'); performance.mark('clay:activation-request'); }
73
101
  ws.send(JSON.stringify({ type: 'switch_session', id: id, paneActivation: target }));
74
102
  return true;
75
103
  }
@@ -87,6 +115,7 @@ export function filterPaneActivation(message) {
87
115
  if (message.type === 'pane_activation_started') return true;
88
116
  if (message.type === 'pane_activation_snapshot_done') {
89
117
  if (!pending) return true;
118
+ if (typeof performance !== 'undefined' && performance.mark) { performance.clearMarks('clay:snapshot-received'); performance.mark('clay:snapshot-received'); }
90
119
  var draft = (store.get('paneRichDrafts') || {})[String(store.get('myUserId')) + ':' + expected.sessionOriginId];
91
120
  restoreComposerDraft(draft || { text: '', start: 0, end: 0 });
92
121
  function paint() {
@@ -95,10 +124,20 @@ export function filterPaneActivation(message) {
95
124
  var messages = document.getElementById('messages');
96
125
  if (!samePaneTarget(current, expected) ||
97
126
  store.get('activeSessionId') !== expected.sessionId || store.get('sessionOriginId') !== expected.sessionOriginId || store.get('replayingHistory') ||
98
- !messages || messages.dataset.controlSessionId !== String(expected.sessionId) || messages.dataset.controlSessionOrigin !== expected.sessionOriginId) { failed(expected.requestId); return; }
127
+ !messages || messages.dataset.controlSessionId !== String(expected.sessionId) || messages.dataset.controlSessionOrigin !== expected.sessionOriginId) {
128
+ failed(expected.requestId, 'snapshot-state-mismatch: active=' + store.get('activeSessionId') + ', origin=' + store.get('sessionOriginId') + ', messages=' + (messages && messages.dataset.controlSessionId) + ', messageOrigin=' + (messages && messages.dataset.controlSessionOrigin));
129
+ return;
130
+ }
131
+ var position = (store.get('paneRetainedPositions') || {})[String(store.get('myUserId')) + ':' + expected.sessionOriginId];
132
+ if (position && Number.isFinite(position.top)) {
133
+ disarmStickyBottom();
134
+ messages.scrollTop = position.top;
135
+ store.set({ paneLastUserScrollTop: position.top });
136
+ }
99
137
  clearDeadline();
100
138
  store.set({ paneCommittedBinding: expected, panePendingBinding: null, paneReady: true });
101
139
  block(false);
140
+ if (typeof performance !== 'undefined' && performance.mark) { performance.clearMarks('clay:pane-ready'); performance.mark('clay:pane-ready'); }
102
141
  var frame = window.frameElement;
103
142
  if (frame) frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-ready'));
104
143
  updateContextPanel();
@@ -167,7 +206,7 @@ export function initPaneActivation() {
167
206
  },
168
207
  activate: activatePaneSession,
169
208
  checkDraft: function () { if (!store.get('panePendingBinding')) { var draft = takeComposerDraft(); restoreComposerDraft(draft); } },
170
- suspend: function () { store.set({ paneSuspended: true, paneReady: false }); block(true); },
209
+ suspend: suspendPane,
171
210
  };
172
211
  // A pointer that began on the previous conversation cannot become a Stop or
173
212
  // approval click on the replacement conversation after hydration.
@@ -56,14 +56,23 @@ export function paneTarget(state, sessions, id) {
56
56
  // Ordinary reusable panes have a live document binding; legacy/canonical frames
57
57
  // retain their existing URL and receipt checks in their own bridge paths.
58
58
  export function livePaneBinding(frame, message) {
59
- if (!frame || !frame.clayPane) return false;
59
+ if (!frame || !frame.clayPane || frame.closest && frame.closest('.split-pane-cached')) return false;
60
60
  var api = frame.clayPane, live = api.identity();
61
61
  var state = store.snap(), split = state.splitPanes;
62
+ if (!state.connected) return false;
62
63
  var target = live && paneTarget(state, state.paneBindingSessions || [], live.sessionId);
64
+ var inLegacySplit = !!(live && split && split.panes.some(function (pane) { return pane.slug === live.projectSlug && pane.sessionId === live.sessionId; }));
65
+ var activePresentation = (state.projectSplitViews || []).find(function (view) { return view.id === state.projectSplitViewActiveId; });
66
+ var inPresentation = !!(live && live.binding && !split && activePresentation && activePresentation.sessions.some(function (session) {
67
+ return session.id === live.sessionId && session.sessionOriginId === live.binding.sessionOriginId;
68
+ }));
69
+ var inSingletonPresentation = !!(live && live.binding && !split && !activePresentation &&
70
+ state.projectSplitViewSingletonOrigin === live.binding.sessionOriginId &&
71
+ live.sessionId === Number(frame.dataset.sessionId) && live.binding.sessionOriginId === frame.dataset.sessionOriginId);
63
72
  return api.source === frame.contentWindow && api.document === frame.contentDocument && live && live.ready &&
64
73
  samePaneTarget(target, live.binding) &&
65
74
  live.ownerId === (state.myUserId || null) && live.projectSlug === state.currentSlug &&
66
- split && split.panes.some(function (pane) { return pane.slug === live.projectSlug && pane.sessionId === live.sessionId; }) &&
75
+ (inLegacySplit || inPresentation || inSingletonPresentation) &&
67
76
  (!message || samePaneBinding(live.binding, message.paneBinding));
68
77
  }
69
78
 
@@ -77,6 +86,7 @@ export function currentPaneReview(container) {
77
86
  }
78
87
 
79
88
  export function paneCallbackCurrent(binding) {
89
+ if (store.get('paneSuspended')) return false;
80
90
  var current = store.get('panePendingBinding') || store.get('paneCommittedBinding');
81
91
  return current ? samePaneBinding(current, binding) : !binding;
82
92
  }
@@ -5,9 +5,11 @@ import { forwardStudioPresentation } from './clay-studio-render.js';
5
5
  // Same-origin messages only, both directions.
6
6
 
7
7
  import { store } from './store.js';
8
+ import { beginPaneLayoutScrollLock, endPaneLayoutScrollLock } from './chat-render-runtime.js';
8
9
  import { paneMessageBinding, samePaneBinding } from './pane-binding.js';
9
10
  import { getEffectiveContextView } from './context-view-preference.js';
10
11
  import { forceExternalLinkToNewTab } from './pane-links.js';
12
+ import { toggleSearch, controlPaneSearch } from './session-search.js';
11
13
 
12
14
  export function reportPaneContext(data) {
13
15
  if (!store.get('paneMode') || window.parent === window) return;
@@ -71,8 +73,15 @@ export function initPaneBridge() {
71
73
  window.addEventListener("message", function (event) {
72
74
  if (event.origin !== window.location.origin) return;
73
75
  var msg = event.data;
74
- if (!msg || msg.type !== "clay-pane-toggle-context") return;
75
- if (event.source !== window.parent || store.get('paneCommittedBinding') && !samePaneBinding(paneMessageBinding(), msg.paneBinding)) return;
76
- togglePaneContextPanel();
76
+ if (!msg || (msg.type !== "clay-pane-toggle-context" && msg.type !== "clay-pane-toggle-search" && msg.type !== "clay-pane-search-command" && msg.type !== "clay-pane-layout-resize")) return;
77
+ var committed = store.get('paneCommittedBinding');
78
+ if (event.source !== window.parent || !committed || !paneMessageBinding() ||
79
+ !samePaneBinding(committed, msg.paneBinding) || !samePaneBinding(paneMessageBinding(), msg.paneBinding)) return;
80
+ if (msg.type === "clay-pane-layout-resize") {
81
+ if (msg.phase === 'begin') beginPaneLayoutScrollLock(msg.token, msg.scrollTop);
82
+ else if (msg.phase === 'end') endPaneLayoutScrollLock(msg.token);
83
+ } else if (msg.type === "clay-pane-toggle-context") togglePaneContextPanel();
84
+ else if (msg.type === "clay-pane-toggle-search") toggleSearch();
85
+ else controlPaneSearch(msg.action, msg.query);
77
86
  });
78
87
  }
@@ -0,0 +1,32 @@
1
+ import { store } from './store.js';
2
+
3
+ // Executable initializers belong to this module; the store holds only IDs.
4
+ var initializers = [];
5
+
6
+ // Parent-owned workbench chrome is unnecessary for a pane's first transcript.
7
+ // Keep the same initializers, but run them one task at a time after its exact
8
+ // authenticated activation has made the conversation interactive.
9
+ function drain() {
10
+ var state = store.snap(), boot = state.paneDeferredBootstrap;
11
+ if (!boot || boot.scheduled || !boot.jobs.length || !state.connected || !state.paneReady || state.paneSuspended) return;
12
+ store.set({ paneDeferredBootstrap: Object.assign({}, boot, { scheduled: true }) });
13
+ setTimeout(function () {
14
+ var latest = store.snap(), pending = latest.paneDeferredBootstrap;
15
+ if (!pending) return;
16
+ var jobs = pending.jobs.slice(), job = latest.connected && latest.paneReady && !latest.paneSuspended ? jobs.shift() : null;
17
+ store.set({ paneDeferredBootstrap: Object.assign({}, pending, { scheduled: false, jobs: jobs }) });
18
+ try { if (job !== null && initializers[job]) { var initialize = initializers[job]; initializers[job] = null; initialize(); } } finally { drain(); }
19
+ }, 0);
20
+ }
21
+ export function initializeAfterPaneReady(jobs) {
22
+ if (!store.get('paneMode')) { jobs.forEach(function (job) { job(); }); return; }
23
+ var boot = store.get('paneDeferredBootstrap');
24
+ if (!boot) {
25
+ boot = { jobs: [], scheduled: false };
26
+ store.subscribe(function (next, previous) {
27
+ if (next.paneReady !== previous.paneReady || next.connected !== previous.connected || next.paneSuspended !== previous.paneSuspended) drain();
28
+ });
29
+ }
30
+ store.set({ paneDeferredBootstrap: Object.assign({}, boot, { jobs: boot.jobs.concat(jobs.map(function (job) { var id = initializers.length; initializers.push(job); return id; })) }) });
31
+ drain();
32
+ }
@@ -0,0 +1,35 @@
1
+ // In-flight promises are module-owned resources, never store data.
2
+ var featureRequests = {};
3
+
4
+ // Only server-authored, inert feature tags are eligible. Parent documents retain
5
+ // their normal vendor loading; pane documents load these groups on first use.
6
+ export function ensurePaneFeatureAssets(group) {
7
+ if (['editor', 'mermaid', 'qr'].indexOf(group) === -1) return Promise.reject(new Error('Unknown feature assets'));
8
+ var existing = featureRequests[group];
9
+ if (existing) return existing;
10
+ var tags = Array.from(document.querySelectorAll('script[data-pane-asset-group="' + group + '"]'));
11
+ var promise = tags.reduce(function (previous, tag) {
12
+ return previous.then(function () {
13
+ if (tag.dataset.loaded === 'true') return;
14
+ return new Promise(function (resolve, reject) {
15
+ var script = document.createElement('script'), timer;
16
+ function failed() {
17
+ clearTimeout(timer); script.onload = null; script.onerror = null; script.remove();
18
+ reject(new Error('Could not load ' + group + ' assets. Try again.'));
19
+ }
20
+ script.src = tag.dataset.src; script.async = false;
21
+ script.onload = function () { clearTimeout(timer); tag.dataset.loaded = 'true'; resolve(); };
22
+ script.onerror = failed; timer = setTimeout(failed, 15000);
23
+ tag.after(script);
24
+ });
25
+ });
26
+ }, Promise.resolve());
27
+ promise = promise.then(function () {
28
+ var value = group === 'editor' ? window.CodeMirror : group === 'mermaid' ? window.mermaid : window.qrcode;
29
+ if (!value) throw new Error('Could not load ' + group + ' assets. Try again.');
30
+ }).catch(function (error) {
31
+ delete featureRequests[group]; throw error;
32
+ });
33
+ featureRequests[group] = promise;
34
+ return promise;
35
+ }
@@ -59,9 +59,14 @@ export function handlePaneFileMessage(event, host, openFile, openFiles) {
59
59
  if (!msg || msg.type !== "clay-pane-open-file") return false;
60
60
  var frame = paneFrame(host, event.source);
61
61
  var state = store.snap();
62
- var pane = matchingPane(frame, state.splitPanes);
63
- if (frame && frame.clayPane && !livePaneBinding(frame, msg)) return true;
64
- if (!pane || !hasLivePaneSource(frame, pane) || String(pane.slug) !== String(state.currentSlug) || msg.projectSlug !== frame.dataset.projectSlug || String(msg.sessionId) !== String(frame.dataset.sessionId) || !validLocation(msg.line) || !validLocation(msg.column)) return true;
62
+ // A live reusable pane is authorized by its document binding, which covers
63
+ // legacy splits, saved views and single-session views. Other frames keep the
64
+ // legacy split membership and URL checks.
65
+ var live = frame && frame.clayPane;
66
+ var pane = live ? { slug: frame.dataset.projectSlug, sessionId: frame.dataset.sessionId } : matchingPane(frame, state.splitPanes);
67
+ if (live && !livePaneBinding(frame, msg)) return true;
68
+ if (live && Number(msg.sessionId) !== live.identity().sessionId) return true;
69
+ if (!pane || (!live && !hasLivePaneSource(frame, pane)) || String(pane.slug) !== String(state.currentSlug) || msg.projectSlug !== frame.dataset.projectSlug || String(msg.sessionId) !== String(frame.dataset.sessionId) || !validLocation(msg.line) || !validLocation(msg.column)) return true;
65
70
  if (typeof msg.path !== "string" || !msg.path) return true;
66
71
  if (!state.connected || typeof openFile !== "function") return true;
67
72
  // The frame/session association was validated above. The host browser owns
@@ -0,0 +1,12 @@
1
+ import { samePaneBinding } from './pane-binding.js';
2
+
3
+ function acceptsPaneSearchResults(state, msg) {
4
+ var binding = state.paneCommittedBinding;
5
+ var receipt = state.paneSearchReceipt;
6
+ return !!(state.paneReady && !state.panePendingBinding && !state.paneSuspended && binding && receipt &&
7
+ receipt.epoch === state.paneSearchEpoch && samePaneBinding(receipt.binding, binding) &&
8
+ Number(binding.sessionId) === Number(msg.sessionId) && Number(binding.sessionId) === Number(state.activeSessionId) &&
9
+ receipt.query === msg.query);
10
+ }
11
+
12
+ export { acceptsPaneSearchResults };