clay-server 5.3.0-beta.5 → 5.3.0-beta.7

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 (185) hide show
  1. package/lib/clay-product-control-tools.js +2 -2
  2. package/lib/clay-product-control.js +1 -1
  3. package/lib/clay-screen-controls.js +1 -0
  4. package/lib/clay-split-view-awareness.js +5 -2
  5. package/lib/clay-split-view-controls.js +14 -7
  6. package/lib/clay-split-view-input.js +5 -4
  7. package/lib/clay-split-view-render-proof.js +14 -4
  8. package/lib/clay-view-workspace-input.js +71 -0
  9. package/lib/project-admin-overview.js +22 -11
  10. package/lib/project-background-tasks.js +76 -0
  11. package/lib/project-connection.js +1 -1
  12. package/lib/project-file-history.js +11 -9
  13. package/lib/project-file-watch.js +26 -7
  14. package/lib/project-filesystem.js +5 -4
  15. package/lib/project-sessions.js +12 -5
  16. package/lib/project-shared-browser.js +7 -3
  17. package/lib/project-split-view-document.js +16 -2
  18. package/lib/project-split-views.js +27 -297
  19. package/lib/project-user-message.js +4 -3
  20. package/lib/project-vendor-login.js +2 -2
  21. package/lib/project-view-input.js +11 -0
  22. package/lib/project-view-pair.js +26 -0
  23. package/lib/project-view-store.js +348 -0
  24. package/lib/project-view-terminal-binding.js +16 -0
  25. package/lib/project-view-workspace.js +126 -0
  26. package/lib/project-worker-conversation.js +5 -0
  27. package/lib/project.js +1 -0
  28. package/lib/public/app.js +9 -0
  29. package/lib/public/css/contextual-clay.css +2 -1
  30. package/lib/public/css/issues.css +3 -0
  31. package/lib/public/css/mate-conversation.css +1 -1
  32. package/lib/public/css/pane.css +5 -1
  33. package/lib/public/css/pending-message-queue.css +24 -19
  34. package/lib/public/css/project-admin-overview.css +42 -13
  35. package/lib/public/css/right-workbench.css +0 -1
  36. package/lib/public/css/scheduled-tasks.css +7 -0
  37. package/lib/public/css/shared-browser.css +31 -8
  38. package/lib/public/css/split-view-layout.css +129 -10
  39. package/lib/public/css/tool-pane.css +124 -0
  40. package/lib/public/index.html +6 -6
  41. package/lib/public/modules/agent-message-bubbles.js +96 -0
  42. package/lib/public/modules/app-header.js +6 -0
  43. package/lib/public/modules/app-messages.js +26 -9
  44. package/lib/public/modules/background-task-previews.js +8 -0
  45. package/lib/public/modules/background-task-viewer.js +97 -0
  46. package/lib/public/modules/background-tasks-ui.js +34 -125
  47. package/lib/public/modules/chat-bubble-renderer.js +11 -5
  48. package/lib/public/modules/clay-file-links.js +2 -1
  49. package/lib/public/modules/clay-split-view-screen.js +6 -3
  50. package/lib/public/modules/clay-tool-pane-screen.js +35 -0
  51. package/lib/public/modules/contextual-clay-geometry.js +7 -3
  52. package/lib/public/modules/file-history-snapshot.js +12 -0
  53. package/lib/public/modules/file-pane-entrypoints.js +55 -0
  54. package/lib/public/modules/file-pane-history.js +78 -0
  55. package/lib/public/modules/file-pane-instances.js +140 -0
  56. package/lib/public/modules/file-pane-links.js +41 -0
  57. package/lib/public/modules/file-pane-render.js +48 -0
  58. package/lib/public/modules/file-pane-review.js +26 -0
  59. package/lib/public/modules/file-pane-toolbar.js +54 -0
  60. package/lib/public/modules/file-viewer-source.js +70 -0
  61. package/lib/public/modules/filebrowser.js +43 -82
  62. package/lib/public/modules/git-panel.js +5 -0
  63. package/lib/public/modules/issues.js +3 -4
  64. package/lib/public/modules/linear-panel.js +2 -2
  65. package/lib/public/modules/mate-knowledge-workbench.js +10 -6
  66. package/lib/public/modules/notifications.js +4 -1
  67. package/lib/public/modules/pane-activation.js +7 -3
  68. package/lib/public/modules/pane-binding.js +5 -1
  69. package/lib/public/modules/pane-browser-bridge.js +6 -2
  70. package/lib/public/modules/pane-browser-transport.js +110 -0
  71. package/lib/public/modules/pane-header-tooltips.js +32 -0
  72. package/lib/public/modules/pending-message-queue-drag.js +69 -0
  73. package/lib/public/modules/pending-message-queue.js +3 -33
  74. package/lib/public/modules/permission-control.js +6 -5
  75. package/lib/public/modules/plantuml-preview.js +12 -12
  76. package/lib/public/modules/project-admin-overview.js +125 -10
  77. package/lib/public/modules/project-grouping.js +9 -0
  78. package/lib/public/modules/right-workbench.js +34 -15
  79. package/lib/public/modules/scheduled-tasks.js +7 -6
  80. package/lib/public/modules/server-settings-controls.js +33 -4
  81. package/lib/public/modules/session-folder-toolbar.js +1 -1
  82. package/lib/public/modules/session-tool-item.js +86 -0
  83. package/lib/public/modules/settings-select.js +2 -1
  84. package/lib/public/modules/shared-browser-tabs.js +37 -21
  85. package/lib/public/modules/shared-browser.js +69 -9
  86. package/lib/public/modules/sidebar-projects.js +1 -10
  87. package/lib/public/modules/split-pane-renderer.js +1 -0
  88. package/lib/public/modules/split-session-empty-pane.js +47 -0
  89. package/lib/public/modules/split-view-create-menu.js +74 -65
  90. package/lib/public/modules/split-view-drag-preview.js +90 -0
  91. package/lib/public/modules/split-view-layout-client.js +1 -1
  92. package/lib/public/modules/split-view-layout-scope.js +4 -0
  93. package/lib/public/modules/split-view-picker-actions.js +71 -0
  94. package/lib/public/modules/split-view-picker-render.js +81 -0
  95. package/lib/public/modules/split-view-presentation-drag.js +36 -13
  96. package/lib/public/modules/split-view-presentation-drop.js +21 -18
  97. package/lib/public/modules/split-view-presentation-renderer.js +22 -17
  98. package/lib/public/modules/split-view-presentations.js +25 -33
  99. package/lib/public/modules/split-view-worker-activity.js +2 -2
  100. package/lib/public/modules/split-view-worker-transcript.js +70 -33
  101. package/lib/public/modules/split-view-workers.js +6 -4
  102. package/lib/public/modules/split-view.js +9 -2
  103. package/lib/public/modules/sticky-notes-browser.js +2 -2
  104. package/lib/public/modules/terminal-viewport.js +14 -0
  105. package/lib/public/modules/terminal.js +49 -23
  106. package/lib/public/modules/theme-palette.js +177 -0
  107. package/lib/public/modules/theme.js +2 -180
  108. package/lib/public/modules/tool-pane-chrome.js +88 -0
  109. package/lib/public/modules/tool-pane-drag.js +134 -0
  110. package/lib/public/modules/tool-pane-layout.js +95 -0
  111. package/lib/public/modules/tool-pane-legacy.js +60 -0
  112. package/lib/public/modules/tool-pane-tree.js +51 -0
  113. package/lib/public/modules/tools.js +5 -89
  114. package/lib/public/modules/tooltip.js +32 -6
  115. package/lib/public/modules/view-legacy-coordinates.js +16 -0
  116. package/lib/public/modules/view-mutations.js +24 -0
  117. package/lib/public/modules/view-pair-presentation.js +74 -0
  118. package/lib/public/modules/view-pair-validation.js +12 -0
  119. package/lib/public/modules/view-presentation-state.js +37 -0
  120. package/lib/public/modules/view-workspace-controller.js +210 -0
  121. package/lib/public/modules/view-workspace-state.js +103 -0
  122. package/lib/public/modules/view-workspace-validation.js +47 -0
  123. package/lib/public/modules/wireframe-workbench.js +13 -2
  124. package/lib/public/modules/workbench-layout-preference.js +12 -5
  125. package/lib/public/modules/workbench-panels.js +21 -0
  126. package/lib/public/modules/worker-message-previews.js +39 -0
  127. package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
  128. package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
  129. package/lib/public/pane-assets/chunk-6QFLYOVY.js +1 -0
  130. package/lib/public/pane-assets/chunk-7OVNC5HT.js +1 -0
  131. package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
  132. package/lib/public/pane-assets/chunk-EMLN2Y7L.js +1 -0
  133. package/lib/public/pane-assets/chunk-ENPAHXLB.js +1 -0
  134. package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
  135. package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
  136. package/lib/public/pane-assets/chunk-FQTNR4ZS.js +1 -0
  137. package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
  138. package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
  139. package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
  140. package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
  141. package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
  142. package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
  143. package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
  144. package/lib/public/pane-assets/chunk-R7QVVFUG.js +1 -0
  145. package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
  146. package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
  147. package/lib/public/pane-assets/chunk-SJOT74VW.js +2 -0
  148. package/lib/public/pane-assets/chunk-SJTGHIN5.js +1 -0
  149. package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
  150. package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
  151. package/lib/public/pane-assets/chunk-UPXU2IPC.js +1 -0
  152. package/lib/public/pane-assets/chunk-VEBYD7TL.js +1 -0
  153. package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
  154. package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
  155. package/lib/public/pane-assets/manifest.json +438 -381
  156. package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
  157. package/lib/public/style.css +1 -0
  158. package/lib/sdk-message-processor.js +5 -2
  159. package/lib/server-storage.js +27 -3
  160. package/lib/server-workbench-layout-settings.js +3 -3
  161. package/lib/server.js +4 -2
  162. package/lib/sessions.js +1 -1
  163. package/lib/split-view-layout.js +11 -7
  164. package/lib/storage-usage.js +29 -7
  165. package/lib/terminal-manager.js +2 -2
  166. package/lib/users-workbench-layout-preferences.js +14 -4
  167. package/lib/workbench-window-layout.js +30 -0
  168. package/lib/worker-message-previews.js +50 -0
  169. package/lib/ws-schema.js +1 -1
  170. package/lib/yoke/adapters/claude-worker.js +8 -0
  171. package/lib/yoke/adapters/claude.js +17 -2
  172. package/lib/yoke/claude-task-reader.js +94 -0
  173. package/lib/yoke/interface.js +1 -0
  174. package/package.json +1 -1
  175. package/lib/public/pane-assets/app-6H5S2Y3Z.js +0 -1
  176. package/lib/public/pane-assets/chunk-3QV5SDC4.js +0 -1
  177. package/lib/public/pane-assets/chunk-4FM3U3DW.js +0 -1
  178. package/lib/public/pane-assets/chunk-7GZH3FZY.js +0 -1
  179. package/lib/public/pane-assets/chunk-AGJTF5WQ.js +0 -1
  180. package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
  181. package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
  182. package/lib/public/pane-assets/chunk-NPKRTUNG.js +0 -2
  183. package/lib/public/pane-assets/chunk-VGA2Y3D2.js +0 -2
  184. package/lib/public/pane-assets/chunk-WKO3T75E.js +0 -167
  185. package/lib/public/pane-assets/style-TSRDU54Z.css +0 -1
@@ -1,3 +1,7 @@
1
+ import { independentFilesActive, closeActiveFilePane, expandActiveFilePane, reopenIndependentFile, refreshIndependentFile, presentIndependentFile } from './file-pane-entrypoints.js';
2
+ import { openIndependentFile, routeFilePaneMessage } from './file-pane-instances.js';
3
+ import { activeSavedView } from './view-workspace-state.js';
4
+ import { mapExtToLanguage, renderCodeWithLineNumbers, renderLargeTextFile, formatSize } from './file-viewer-source.js';
1
5
  import { presentedDocument, refreshPresentedDocument, closePresentedDocument } from './clay-studio-document.js';
2
6
  import { registerStudioPaint } from './clay-studio-render.js';
3
7
  import { iconHtml, refreshIcons } from './icons.js';
@@ -47,7 +51,7 @@ export function initFileBrowser(_ctx) {
47
51
  onFocus: function(path) {
48
52
  claimRightWorkbench("file-viewer");
49
53
  ctx.fileViewerEl.classList.remove("hidden");
50
- if (path !== currentFilePath || (store.get("fileViewerIsHtml") && isRendered && !document.querySelector(".file-viewer-html-preview"))) requestFileContent(path);
54
+ if (path !== currentFilePath || (store.get("fileViewerIsHtml") && isRendered && !ctx.fileViewerEl.querySelector(".file-viewer-html-preview"))) requestFileContent(path);
51
55
  },
52
56
  onEmpty: function() { resetMarkdownViewModes(); teardownFileViewer(); },
53
57
  });
@@ -499,24 +503,30 @@ function ensureRenderedMarkdown() {
499
503
  }
500
504
 
501
505
  export function closeFileViewer() {
506
+ if (independentFilesActive()) return closeActiveFilePane();
502
507
  releaseRightWorkbench("file-viewer");
503
508
  teardownFileViewer();
504
509
  }
505
510
 
506
511
  function teardownFileViewer() {
507
512
  cancelPlantUmlPreview();
508
- var htmlFrame = document.querySelector(".file-viewer-html-preview");
513
+ var htmlFrame = ctx.fileViewerEl.querySelector(".file-viewer-html-preview");
509
514
  if (htmlFrame) htmlFrame.remove();
510
515
  if (currentFilePath && isFollowingMarkdown(currentFilePath)) cancelMarkdownFollow();
511
516
  sendUnwatch();
512
517
  inlineDiffActive = false;
513
518
  exitMarkdownSlides();
514
519
  ctx.fileViewerEl.classList.remove("file-viewer-wide");
515
- setFileViewerFullscreen(false);
520
+ setLegacyFileViewerFullscreen(false);
516
521
  ctx.fileViewerEl.classList.add("hidden");
517
522
  }
518
523
 
519
524
  export function setFileViewerFullscreen(enabled) {
525
+ if (independentFilesActive()) return expandActiveFilePane(enabled);
526
+ return setLegacyFileViewerFullscreen(enabled);
527
+ }
528
+
529
+ function setLegacyFileViewerFullscreen(enabled) {
520
530
  var isFullscreen = !!enabled;
521
531
  var viewer = ctx && ctx.fileViewerEl ? ctx.fileViewerEl : document.getElementById("file-viewer");
522
532
  var button = document.getElementById("file-viewer-fullscreen");
@@ -575,6 +585,11 @@ var pendingRenderedOpen = false;
575
585
 
576
586
  export function openFile(filePath, opts) {
577
587
  if (!filePath) return;
588
+ if (activeSavedView() && !store.get('paneMode')) {
589
+ var opened = openIndependentFile(filePath, opts);
590
+ if (opened !== false) closePresentedDocument();
591
+ return opened;
592
+ }
578
593
  var state = store.snap();
579
594
  if (!state.connected) return false;
580
595
  if (opts && opts.projectSlug && opts.projectSlug !== state.currentSlug) return false;
@@ -597,6 +612,7 @@ export function openFile(filePath, opts) {
597
612
  }
598
613
 
599
614
  export function reopenFileViewer() {
615
+ if (independentFilesActive()) return reopenIndependentFile();
600
616
  var workbenchRevision = claimRightWorkbench("file-viewer");
601
617
  store.set({ fileViewerOpenRevision: workbenchRevision });
602
618
  if (!focusedFileViewerTab()) return false;
@@ -616,6 +632,7 @@ export function reopenFileViewer() {
616
632
 
617
633
  export function openWorkingTreeDiff(diff) {
618
634
  if (!diff || !diff.path) return;
635
+ if (independentFilesActive()) return presentIndependentFile({ path: diff.path, content: diff.newContent, binary: diff.binary }, diff.binary ? null : { oldStr: diff.oldContent || "", newStr: diff.newContent || "" }, diff.review);
619
636
  var workbenchRevision = claimRightWorkbench("file-viewer");
620
637
  store.set({ fileViewerOpenRevision: workbenchRevision });
621
638
  openFileViewerTab(diff.path);
@@ -636,6 +653,7 @@ export function openWorkingTreeDiff(diff) {
636
653
 
637
654
  export function presentMarkdownEdit(msg) {
638
655
  if (!msg || !beginMarkdownPresentation(msg.path)) return;
656
+ if (independentFilesActive()) return presentIndependentFile(msg);
639
657
  var workbenchRevision = claimRightWorkbench("file-viewer");
640
658
  store.set({ fileViewerOpenRevision: workbenchRevision });
641
659
  openFileViewerTab(msg.path, { content: msg.content });
@@ -739,7 +757,7 @@ function requestFileContent(filePath) {
739
757
  if (refreshPresentedDocument(filePath)) return;
740
758
  var requestId = "file-read-" + Date.now() + "-" + Math.random().toString(36).slice(2);
741
759
  var state = store.snap();
742
- store.set({ fileReadRequest: { requestId: requestId, path: filePath, projectSlug: state.currentSlug || null, sessionId: state.activeSessionId != null ? String(state.activeSessionId) : null, accountId: state.myUserId || null, workbenchRevision: rightWorkbenchRevisionFor('file-viewer') } });
760
+ store.set({ fileReadRequest: { requestId: requestId, path: filePath, projectSlug: state.currentSlug || null, sessionId: state.activeSessionId != null ? String(state.activeSessionId) : null, accountId: state.myUserId || null, viewId: state.projectSplitViewActiveId || null, socketGeneration: state.projectSplitViewSocketGeneration, workbenchRevision: rightWorkbenchRevisionFor('file-viewer') } });
743
761
  var pending = store.get('pendingFileNavigation');
744
762
  if (pending && pending.path === filePath) store.set({ pendingFileNavigation: Object.assign({}, pending, { requestId: requestId }) });
745
763
  if (ctx.ws && ctx.connected) {
@@ -750,6 +768,7 @@ function requestFileContent(filePath) {
750
768
  var pendingRefresh = false;
751
769
 
752
770
  export function refreshIfOpen(filePath) {
771
+ if (independentFilesActive()) return refreshIndependentFile(filePath);
753
772
  if (!currentFilePath || ctx.fileViewerEl.classList.contains("hidden")) return;
754
773
  // Don't refresh while history panel or inline diff is showing
755
774
  if (historyVisible || inlineDiffActive) return;
@@ -863,7 +882,7 @@ function renderFilteredTree(container, tree, depth, query) {
863
882
  (function (rowEl, childEl, folderName) {
864
883
  rowEl.addEventListener("click", function (e) {
865
884
  e.stopPropagation();
866
- claimRightWorkbench("file-viewer");
885
+ if (!activeSavedView()) claimRightWorkbench("file-viewer");
867
886
  var isExpanded = rowEl.classList.contains("expanded");
868
887
  rowEl.classList.toggle("expanded");
869
888
  childEl.classList.toggle("hidden", isExpanded);
@@ -886,6 +905,11 @@ function renderFilteredTree(container, tree, depth, query) {
886
905
  (function (filePath, rowEl) {
887
906
  rowEl.addEventListener("click", function (e) {
888
907
  e.stopPropagation();
908
+ if (activeSavedView() && !store.get('paneMode')) {
909
+ openIndependentFile(filePath);
910
+ if (window.innerWidth <= 768) closeSidebar();
911
+ return;
912
+ }
889
913
  claimRightWorkbench("file-viewer");
890
914
  if (followedFileChanged(filePath)) cancelMarkdownFollow();
891
915
  var prev = ctx.fileTreeEl.querySelector(".file-tree-item.active");
@@ -1018,8 +1042,10 @@ function restoreExpanded(expandedSet) {
1018
1042
  }
1019
1043
 
1020
1044
  export function handleFsRead(msg) {
1045
+ if (routeFilePaneMessage(msg)) return;
1021
1046
  var request = store.get('fileReadRequest');
1022
1047
  var state = store.snap();
1048
+ if (request && (request.viewId !== (state.projectSplitViewActiveId || null) || request.socketGeneration !== state.projectSplitViewSocketGeneration)) return;
1023
1049
  if (!request || !state.connected || msg.requestId !== request.requestId || msg.path !== request.path) return;
1024
1050
  if ((request.projectSlug || null) !== (state.currentSlug || null)) return;
1025
1051
  if ((request.sessionId || null) !== (state.activeSessionId == null ? null : String(state.activeSessionId))) return;
@@ -1094,7 +1120,7 @@ function renderEntries(container, entries, depth) {
1094
1120
  (function (dirPath, childEl, rowEl, folderName) {
1095
1121
  rowEl.addEventListener("click", function (e) {
1096
1122
  e.stopPropagation();
1097
- claimRightWorkbench("file-viewer");
1123
+ if (!activeSavedView()) claimRightWorkbench("file-viewer");
1098
1124
  var isExpanded = rowEl.classList.contains("expanded");
1099
1125
  if (isExpanded) {
1100
1126
  rowEl.classList.remove("expanded");
@@ -1134,6 +1160,11 @@ function renderEntries(container, entries, depth) {
1134
1160
  (function (filePath, rowEl) {
1135
1161
  rowEl.addEventListener("click", function (e) {
1136
1162
  e.stopPropagation();
1163
+ if (activeSavedView() && !store.get('paneMode')) {
1164
+ openIndependentFile(filePath);
1165
+ if (window.innerWidth <= 768) closeSidebar();
1166
+ return;
1167
+ }
1137
1168
  claimRightWorkbench("file-viewer");
1138
1169
  if (followedFileChanged(filePath)) cancelMarkdownFollow();
1139
1170
  // Mark active
@@ -1382,6 +1413,7 @@ function pathsReferToSameFile(left, right) {
1382
1413
  }
1383
1414
 
1384
1415
  export function handleFileChanged(msg, presentedSource) {
1416
+ if (routeFilePaneMessage(msg)) return;
1385
1417
  var source = store.get('studioDocumentView');
1386
1418
  if (source && source.descriptor.path === msg.path && (!presentedSource || presentedDocument(msg.path) !== presentedSource)) return;
1387
1419
  if (!msg.path || !pathsReferToSameFile(msg.path, currentFilePath)) return;
@@ -1545,81 +1577,7 @@ function showInlineDiff(oldStr, newStr, review) {
1545
1577
  render();
1546
1578
  }
1547
1579
 
1548
- function mapExtToLanguage(ext) {
1549
- var map = {
1550
- js: "javascript", ts: "typescript", jsx: "javascript", tsx: "typescript",
1551
- py: "python", rb: "ruby", go: "go", rs: "rust", java: "java",
1552
- css: "css", html: "xml", xml: "xml", json: "json", yaml: "yaml",
1553
- yml: "yaml", md: "markdown", sh: "bash", bash: "bash", zsh: "bash",
1554
- sql: "sql", c: "c", cpp: "cpp", h: "c", hpp: "cpp",
1555
- cs: "csharp", swift: "swift", kt: "kotlin", vue: "xml", svelte: "xml"
1556
- };
1557
- return map[ext] || null;
1558
- }
1559
-
1560
- function currentLang() {
1561
- if (!currentFilePath) return null;
1562
- var ext = currentFilePath.split(".").pop().toLowerCase();
1563
- return mapExtToLanguage(ext);
1564
- }
1565
-
1566
- function renderCodeWithLineNumbers(bodyEl, content, ext) {
1567
- var lang = mapExtToLanguage(ext);
1568
- var lines = content.split("\n");
1569
- var lineCount = lines.length;
1570
-
1571
- var viewer = document.createElement("div");
1572
- viewer.className = "file-viewer-code";
1573
-
1574
- var gutter = document.createElement("pre");
1575
- gutter.className = "file-viewer-gutter";
1576
- var nums = [];
1577
- for (var i = 1; i <= lineCount; i++) nums.push(i);
1578
- gutter.textContent = nums.join("\n");
1579
-
1580
- var codeWrap = document.createElement("pre");
1581
- codeWrap.className = "file-viewer-code-content";
1582
- var codeEl = document.createElement("code");
1583
- if (lang) codeEl.className = "language-" + lang;
1584
- codeEl.textContent = content;
1585
- codeWrap.appendChild(codeEl);
1586
-
1587
- viewer.appendChild(gutter);
1588
- viewer.appendChild(codeWrap);
1589
-
1590
- bodyEl.innerHTML = "";
1591
- bodyEl.appendChild(viewer);
1592
-
1593
- if (typeof hljs !== "undefined" && lang) {
1594
- hljs.highlightElement(codeEl);
1595
- }
1596
- }
1597
-
1598
- function renderLargeTextFile(bodyEl, content, size) {
1599
- var viewer = document.createElement("div");
1600
- viewer.className = "file-viewer-large-text";
1601
-
1602
- var notice = document.createElement("div");
1603
- notice.className = "file-viewer-large-notice";
1604
- notice.innerHTML = iconHtml("file-text") +
1605
- "<span>Large file (" + formatSize(size || 0) + ") — showing plain text for performance</span>";
1606
-
1607
- var codeWrap = document.createElement("pre");
1608
- var codeEl = document.createElement("code");
1609
- codeEl.textContent = content;
1610
- codeWrap.appendChild(codeEl);
1611
-
1612
- viewer.appendChild(notice);
1613
- viewer.appendChild(codeWrap);
1614
- bodyEl.innerHTML = "";
1615
- bodyEl.appendChild(viewer);
1616
- }
1617
-
1618
- function formatSize(bytes) {
1619
- if (bytes < 1024) return bytes + " B";
1620
- if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
1621
- return (bytes / 1048576).toFixed(1) + " MB";
1622
- }
1580
+ function currentLang() { return currentFilePath ? mapExtToLanguage(currentFilePath.split('.').pop().toLowerCase()) : null; }
1623
1581
 
1624
1582
  // --- File edit history ---
1625
1583
 
@@ -1639,6 +1597,7 @@ function requestGitDiff(hash, hash2) {
1639
1597
  }
1640
1598
 
1641
1599
  export function handleFileHistory(msg) {
1600
+ if (routeFilePaneMessage(msg)) return;
1642
1601
  currentHistoryEntries = msg.entries || [];
1643
1602
  var historyBtn = document.getElementById("file-viewer-history");
1644
1603
 
@@ -1655,6 +1614,7 @@ export function handleFileHistory(msg) {
1655
1614
  }
1656
1615
 
1657
1616
  export function handleGitDiff(msg) {
1617
+ if (routeFilePaneMessage(msg)) return;
1658
1618
  if (msg.hash && msg.diff !== undefined) {
1659
1619
  var key = msg.hash2 ? msg.hash + ".." + msg.hash2 : msg.hash;
1660
1620
  gitDiffCache[key] = msg.diff;
@@ -1673,6 +1633,7 @@ function requestFileAt(hash) {
1673
1633
  }
1674
1634
 
1675
1635
  export function handleFileAt(msg) {
1636
+ if (routeFilePaneMessage(msg)) return;
1676
1637
  if (msg.hash && msg.content !== undefined) {
1677
1638
  fileAtCache[msg.hash] = msg.content;
1678
1639
  }
@@ -2370,7 +2331,7 @@ function renderViewFileContent(entry, content) {
2370
2331
  bodyEl.appendChild(codeContainer);
2371
2332
  }
2372
2333
 
2373
- function navigateToEdit(edit) {
2334
+ export function navigateToEdit(edit) {
2374
2335
  // If already in the same session, scroll directly without replaying history
2375
2336
  if (ctx.activeSessionId === edit.sessionLocalId) {
2376
2337
  scrollToToolElement(edit.toolId, edit.assistantUuid);
@@ -1,3 +1,4 @@
1
+ import { fileEntryContext } from './file-pane-entrypoints.js';
1
2
  import { currentGithubWorkMarkup, refreshGithubWork, resetGithubWork } from './session-github.js';
2
3
  import { store } from './store.js';
3
4
  import { refreshIcons, iconHtml } from './icons.js';
@@ -332,7 +333,9 @@ function runAction(action, paths) {
332
333
  }
333
334
 
334
335
  function openFileDiff(filePath, reviewIndex) {
336
+ var receipt = fileEntryContext();
335
337
  requestJson(apiPath("file-diff?path=" + encodeURIComponent(filePath)), { cache: "no-store" }).then(function (result) {
338
+ if (receipt !== fileEntryContext()) return;
336
339
  var paths = currentStatus ? currentStatus.files.map(function (file) { return file.path; }) : [filePath];
337
340
  var index = typeof reviewIndex === "number" ? reviewIndex : paths.indexOf(filePath);
338
341
  if (index < 0) index = 0;
@@ -350,8 +353,10 @@ function openFileDiff(filePath, reviewIndex) {
350
353
  }
351
354
 
352
355
  function openSessionDiff(sessionKey, filePath) {
356
+ var receipt = fileEntryContext();
353
357
  var url = apiPath("session-diff?session=" + encodeURIComponent(sessionKey) + "&path=" + encodeURIComponent(filePath));
354
358
  requestJson(url, { cache: "no-store" }).then(function (result) {
359
+ if (receipt !== fileEntryContext()) return;
355
360
  openWorkingTreeDiff(result);
356
361
  showToast("Compared with start of " + (result.sessionTitle || "session"));
357
362
  }).catch(function (err) {
@@ -2,7 +2,7 @@ import { openLinear, initLinearPanel } from './linear-panel.js';
2
2
  import { store } from './store.js';
3
3
  import { getWs } from './ws-ref.js';
4
4
  import { showToast } from './utils.js';
5
- import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
5
+ import { handlesWorkbenchKey, claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
6
6
  import { refreshIcons } from './icons.js';
7
7
  import { enhanceClayLogLinks } from './clay-log-links.js';
8
8
  import { updateIssueLink } from './clay-issue-links.js';
@@ -169,7 +169,7 @@ export function initIssues() {
169
169
  registerRightWorkbench("issues", closeIssues);
170
170
  var el = document.createElement('section');
171
171
  el.id = 'issues-panel'; el.className = 'hidden'; el.setAttribute('aria-label', 'Project Issues');
172
- el.innerHTML = '<header class="issues-topbar"><button class="issues-icon-btn" data-issues-back aria-label="Back to issues" title="Back to issue list"><i data-lucide="arrow-left"></i></button><span class="issues-title"><i data-lucide="circle-dot"></i>Issues</span><span class="issues-subtitle">Manage issues through Driver chat.</span><button type="button" class="issues-icon-btn" data-open-linear>Linear</button><div class="issues-window-actions"><button class="issues-icon-btn" data-issues-wide aria-label="Widen Issues" title="Widen Issues" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button><button class="issues-icon-btn" data-issues-full aria-label="Toggle Issues fullscreen" title="Toggle fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button><button class="issues-icon-btn" data-issues-close aria-label="Close Issues" title="Close Issues"><i data-lucide="x"></i></button></div></header><div class="issues-filters"><label class="issues-search"><i data-lucide="search"></i><input name="query" type="search" placeholder="Search issues" aria-label="Search issues"></label><select name="filter-status" aria-label="Filter status"><option value="">All statuses</option><option value="open">Open</option><option value="in_progress">In progress</option><option value="resolved">Resolved</option><option value="closed">Closed</option></select><select name="filter-type" aria-label="Filter type"><option value="">All types</option><option value="bug">Bug</option><option value="feature">Feature</option><option value="plan">Plan</option></select></div><p class="issues-notice" role="status"></p><main class="issues-content" role="region" aria-live="polite"></main><div class="issues-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="issues-delete-heading"><div class="issues-delete-card"><h2 id="issues-delete-heading">Delete this issue?</h2><p>This removes the issue from the board but preserves its history.</p><p class="issues-delete-title"></p><p class="issues-delete-status" role="status"></p><div><button type="button" data-issue-delete-cancel>Cancel</button><button type="button" data-issue-delete-confirm>Delete issue</button></div></div></div>';
172
+ el.innerHTML = '<header class="issues-topbar"><button class="issues-icon-btn" data-issues-back aria-label="Back to issues" title="Back to issue list"><i data-lucide="arrow-left"></i></button><span class="issues-title"><i data-lucide="circle-dot"></i>Issues</span><span class="issues-subtitle">Manage issues through Driver chat.</span><div class="issues-window-actions"><button class="issues-icon-btn" data-issues-wide aria-label="Widen Issues" title="Widen Issues" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button><button class="issues-icon-btn" data-issues-full aria-label="Toggle Issues fullscreen" title="Toggle fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button><button class="issues-icon-btn" data-issues-close aria-label="Close Issues" title="Close Issues"><i data-lucide="x"></i></button></div></header><div class="issues-filters"><label class="issues-search"><i data-lucide="search"></i><input name="query" type="search" placeholder="Search issues" aria-label="Search issues"></label><select name="filter-status" aria-label="Filter status"><option value="">All statuses</option><option value="open">Open</option><option value="in_progress">In progress</option><option value="resolved">Resolved</option><option value="closed">Closed</option></select><select name="filter-type" aria-label="Filter type"><option value="">All types</option><option value="bug">Bug</option><option value="feature">Feature</option><option value="plan">Plan</option></select><button type="button" class="issues-linear-link" data-open-linear title="Open Linear" aria-label="Open Linear">Linear<i data-lucide="chevron-right"></i></button></div><p class="issues-notice" role="status"></p><main class="issues-content" role="region" aria-live="polite"></main><div class="issues-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="issues-delete-heading"><div class="issues-delete-card"><h2 id="issues-delete-heading">Delete this issue?</h2><p>This removes the issue from the board but preserves its history.</p><p class="issues-delete-title"></p><p class="issues-delete-status" role="status"></p><div><button type="button" data-issue-delete-cancel>Cancel</button><button type="button" data-issue-delete-confirm>Delete issue</button></div></div></div>';
173
173
  host.appendChild(el);
174
174
  el.querySelector('[data-open-linear]').addEventListener('click', function () { openLinear(); });
175
175
  el.addEventListener('input', handleIssueGithubInput);
@@ -226,9 +226,8 @@ export function initIssues() {
226
226
  });
227
227
  store.subscribe(function (state, previous) {
228
228
  if (state.currentSlug !== previous.currentSlug || (previous.connected && !state.connected)) { closeIssues(); store.set({ issuesRequests: {}, issuesSelected: null, issuesCache: {}, issuesMutationRequest: null }); }
229
- if (state.issuesOpen && (state.projectLogsOpen || state.scheduledTasksOpen)) closeIssues();
230
229
  });
231
- document.addEventListener('keydown', function (event) { if (store.get('issuesOpen')) { deleteDialogKeydown(event); if (!event.defaultPrevented && event.key === 'Escape') closeIssues(); } });
230
+ document.addEventListener('keydown', function (event) { if (store.get('issuesOpen') && handlesWorkbenchKey('issues', event)) { deleteDialogKeydown(event); if (!event.defaultPrevented && event.key === 'Escape') closeIssues(); } });
232
231
  }
233
232
  function showIssueEdit(entry) {
234
233
  if (!entry) return;
@@ -4,7 +4,7 @@ import { escapeHtml } from './utils.js';
4
4
  import { renderMarkdown } from './markdown.js';
5
5
  import { refreshIcons } from './icons.js';
6
6
  import { linearIconMarkup } from './session-github.js';
7
- import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
7
+ import { handlesWorkbenchKey, claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
8
8
 
9
9
  function esc(value) { return escapeHtml(String(value || '')).replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }
10
10
  function panel() { return document.getElementById('linear-panel'); }
@@ -145,7 +145,7 @@ export function initLinearPanel() {
145
145
  else if (issue && button.hasAttribute('data-linear-start')) { button.disabled = true; request('linear_start_work', { url: issue.url }); }
146
146
  else if (issue && button.hasAttribute('data-linear-more')) { button.disabled = true; request('linear_read', { url: issue.url, cursor: issue.pageInfo.endCursor }); }
147
147
  });
148
- document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && store.get('linearOpen')) closeLinear(); });
148
+ document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && store.get('linearOpen') && handlesWorkbenchKey('linear', event)) closeLinear(); });
149
149
  store.subscribe(function (state, previous) {
150
150
  if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.dmMode !== previous.dmMode || (previous.connected && !state.connected)) closeLinear();
151
151
  else if (state.linearOpen && (state.githubWorkSessions !== previous.githubWorkSessions || state.activeSessionId !== previous.activeSessionId) && state.linearIssue) renderIssue();
@@ -103,7 +103,7 @@ function bindPanel() {
103
103
  panel.querySelector('[data-knowledge-forward]').addEventListener('click', function () { navigateHistory('forward'); });
104
104
  panel.querySelector('.knowledge-search input').addEventListener('input', scheduleSearch);
105
105
  panel.querySelector('[data-knowledge-wide]').addEventListener('click', function () { var key = workspaceKey(); patchState(key, { wide: !stateFor(key).wide }); renderShell(); });
106
- panel.querySelector('[data-knowledge-full]').addEventListener('click', exitEditor);
106
+ panel.querySelector('[data-knowledge-full]').addEventListener('click', togglePaneExpansion);
107
107
  panel.querySelector('[data-delete-cancel]').addEventListener('click', closeDelete);
108
108
  panel.querySelector('[data-delete-confirm]').addEventListener('click', confirmDelete);
109
109
  panel.addEventListener('click', handlePanelClick);
@@ -326,10 +326,14 @@ function retrySync() {
326
326
  simpleRequest('knowledge_sync', { names: retry.names }, { syncRetry: retry });
327
327
  }
328
328
 
329
+ function togglePaneExpansion() { var key = workspaceKey(), state = stateFor(key);
330
+ if (state.fullscreen) { patchState(key, { paneExpanded: false }); exitEditor(); return; }
331
+ patchState(key, { paneExpanded: !state.paneExpanded }); renderShell();
332
+ }
329
333
  function renderShell() {
330
- var state = stateFor(workspaceKey()); panel.classList.toggle('knowledge-wide', state.wide); panel.classList.toggle('panel-fullscreen', state.fullscreen);
331
- panel.querySelector('[data-knowledge-wide]').setAttribute('aria-pressed', String(state.wide)); panel.querySelector('[data-knowledge-full]').setAttribute('aria-pressed', String(state.fullscreen));
332
- panel.querySelector('[data-knowledge-full]').hidden = !state.fullscreen; panel.querySelector('[data-knowledge-wide]').hidden = state.fullscreen;
334
+ var state = stateFor(workspaceKey()); panel.classList.toggle('knowledge-wide', state.wide); panel.classList.toggle('panel-fullscreen', !!(state.fullscreen || state.paneExpanded));
335
+ panel.querySelector('[data-knowledge-wide]').setAttribute('aria-pressed', String(state.wide)); panel.querySelector('[data-knowledge-full]').setAttribute('aria-pressed', String(!!(state.fullscreen || state.paneExpanded)));
336
+ panel.querySelector('[data-knowledge-full]').hidden = false; panel.querySelector('[data-knowledge-wide]').hidden = state.fullscreen;
333
337
  panel.querySelector('[data-knowledge-back]').disabled = !(state.navigationBack || []).length; panel.querySelector('[data-knowledge-forward]').disabled = !(state.navigationForward || []).length;
334
338
  }
335
339
  function renderStatus() {
@@ -433,12 +437,12 @@ export function openKnowledgeWorkbench() {
433
437
  ensurePanel(); if (!panel || !isMateWorkspace(store.snap())) return; returnFocus = document.activeElement;
434
438
  claimRightWorkbench('mate-knowledge'); panel.classList.remove('hidden'); setKnowledgeNarrowBackground(panel, true); var button = document.getElementById('knowledge-workbench-btn'); if (button) button.classList.add('active');
435
439
  var key = workspaceKey(); var state = stateFor(key); var drafts = Object.assign({}, state.drafts); if (state.selected && drafts[state.selected]) drafts[state.selected] = Object.assign({}, drafts[state.selected], { mode: 'read' });
436
- setState(key, Object.assign({}, state, { drafts: drafts, fullscreen: false, loading: true, error: '' })); simpleRequest('knowledge_list'); if (stateFor(key).query.trim()) { patchState(key, { searching: true }); requestSearch(key, stateFor(key).query, 0, false); } render(); setTimeout(function () { var search = panel.querySelector('.knowledge-search input'); if (search) search.focus(); }, 0);
440
+ setState(key, Object.assign({}, state, { drafts: drafts, fullscreen: false, paneExpanded: false, loading: true, error: '' })); simpleRequest('knowledge_list'); if (stateFor(key).query.trim()) { patchState(key, { searching: true }); requestSearch(key, stateFor(key).query, 0, false); } render(); setTimeout(function () { var search = panel.querySelector('.knowledge-search input'); if (search) search.focus(); }, 0);
437
441
  }
438
442
  export function closeKnowledgeWorkbench(skipSave) {
439
443
  if (!panel) return; var active = document.activeElement; var shouldReturn = skipSave !== true && (!active || active === document.body || panel.contains(active));
440
444
  var key = workspaceKey(); cancelScheduledSearch(); if (deleteTarget) closeDelete(); if (skipSave !== true) { saveScroll(); var selected = stateFor(key).selected; if (selected) saveDraftNow(key, selected); }
441
- patchState(key, { fullscreen: false }); destroyKnowledgeEditor(); panel.classList.add('hidden'); setKnowledgeNarrowBackground(panel, false); releaseRightWorkbench('mate-knowledge');
445
+ patchState(key, { fullscreen: false, paneExpanded: false }); destroyKnowledgeEditor(); panel.classList.add('hidden'); setKnowledgeNarrowBackground(panel, false); releaseRightWorkbench('mate-knowledge');
442
446
  var button = document.getElementById('knowledge-workbench-btn'); if (button) button.classList.remove('active'); if (shouldReturn && returnFocus && returnFocus.isConnected) returnFocus.focus(); returnFocus = null;
443
447
  }
444
448
  export function initMateKnowledgeWorkbench() {
@@ -1,3 +1,4 @@
1
+ import { store } from './store.js';
1
2
  import { initNotificationSettings, saveNotificationPreference, updatePushSettings } from './notification-settings.js';
2
3
  import { initExtensionSettings } from './extension-settings.js';
3
4
  import { copyToClipboard, showToast } from './utils.js';
@@ -71,7 +72,9 @@ export function initNotifications(_ctx) {
71
72
  var $ = ctx.$;
72
73
 
73
74
  // --- Mobile viewport (iOS keyboard handling) ---
74
- if (window.visualViewport) {
75
+ // Embedded panes follow the host viewport. Their layout resize is not a
76
+ // software keyboard opening and must not force the transcript to its tail.
77
+ if (window.visualViewport && !store.get('paneMode')) {
75
78
  var layout = $("layout");
76
79
  var mobileTabBar = document.getElementById("mobile-tab-bar");
77
80
  var keyboardWasOpen = false;
@@ -1,4 +1,5 @@
1
1
  import { paneHeaderRuntime, initPaneHeaderRuntime } from './pane-header-runtime.js';
2
+ import { sendBoundBrowserCommand } from './pane-browser-transport.js';
2
3
  import { disarmStickyBottom } from './chat-render-runtime.js';
3
4
  import { store } from './store.js';
4
5
  import { getWs } from './ws-ref.js';
@@ -173,7 +174,7 @@ export function paneOutgoing(data) {
173
174
  if (message.paneBinding && !samePaneBinding(binding, message.paneBinding)) return null;
174
175
  var gesture = (store.get('paneActionEvents') || []).find(function (entry) { return entry.event.isTrusted && entry.event.eventPhase && samePaneBinding(entry.binding, binding); });
175
176
  var action = store.get('paneCallbackBinding') || gesture && gesture.binding;
176
- var read = /^(get_|list_)|(_get|_list|_options)$/.test(message.type) || message.type === 'load_more_history';
177
+ var read = /^(get_|list_)|(_get|_list|_options)$/.test(message.type) || message.type === 'load_more_history' || message.type === 'background_task_read';
177
178
  if (!message.paneBinding && !read && !samePaneBinding(action, binding)) return null;
178
179
  return JSON.stringify(Object.assign({}, message, { paneBinding: message.paneBinding || action || binding }));
179
180
  }
@@ -206,6 +207,7 @@ export function initPaneActivation() {
206
207
  binding: binding, ready: store.get('connected') && store.get('paneReady') && !store.get('paneSuspended') };
207
208
  },
208
209
  headerRuntime: paneHeaderRuntime,
210
+ browserCommand: sendBoundBrowserCommand,
209
211
  activate: activatePaneSession,
210
212
  checkDraft: function () { if (!store.get('panePendingBinding')) { var draft = takeComposerDraft(); restoreComposerDraft(draft); } },
211
213
  suspend: suspendPane,
@@ -219,7 +221,7 @@ export function initPaneActivation() {
219
221
  event.preventDefault(); event.stopImmediatePropagation();
220
222
  }
221
223
  }, true);
222
- ['click', 'input', 'change', 'keydown', 'keyup', 'submit', 'blur'].forEach(function (name) {
224
+ ['click', 'input', 'change', 'keydown', 'keyup', 'submit', 'blur', 'pointerup'].forEach(function (name) {
223
225
  document.addEventListener(name, function (event) {
224
226
  var keys = store.get('paneHeldKeys') || {};
225
227
  var binding = store.get('paneCommittedBinding');
@@ -227,7 +229,9 @@ export function initPaneActivation() {
227
229
  var staleKey = (name === 'keyup' || name === 'keydown' && event.repeat) && previousKey && !samePaneBinding(previousKey, binding);
228
230
  if (name === 'keydown' && !event.repeat) keys[event.code || event.key] = binding;
229
231
  if (name === 'keyup') delete keys[event.code || event.key];
230
- if (staleKey || store.get('panePendingBinding') || store.get('paneSuspended') || !store.get('paneReady')) {
232
+ // A release from an earlier conversation must not authorize a drag mutation.
233
+ var stalePointer = name === 'pointerup' && !samePaneBinding(store.get('panePointerBinding'), binding);
234
+ if (staleKey || stalePointer || store.get('panePendingBinding') || store.get('paneSuspended') || !store.get('paneReady')) {
231
235
  event.preventDefault(); event.stopImmediatePropagation(); return;
232
236
  }
233
237
  var events = (store.get('paneActionEvents') || []).filter(function (entry) { return entry.event.eventPhase; });
@@ -63,7 +63,11 @@ export function livePaneBinding(frame, message) {
63
63
  var target = live && paneTarget(state, state.paneBindingSessions || [], live.sessionId);
64
64
  var inLegacySplit = !!(live && split && split.panes.some(function (pane) { return pane.slug === live.projectSlug && pane.sessionId === live.sessionId; }));
65
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) {
66
+ var pair = activePresentation && activePresentation.workspace && activePresentation.workspace.linkedPair;
67
+ var linkedPresentation = !!(split && pair && split.groupId === pair.groupId && (state.splitGroups || []).some(function (group) {
68
+ return group.id === pair.groupId && group.pair && (state.paneBindingSessions || []).some(function (session) { return session.id === group.pair.driverId && session.sessionOriginId === pair.driverOriginId; });
69
+ }));
70
+ var inPresentation = !!(live && live.binding && (!split || linkedPresentation) && activePresentation && activePresentation.sessions.some(function (session) {
67
71
  return session.id === live.sessionId && session.sessionOriginId === live.binding.sessionOriginId;
68
72
  }));
69
73
  var inSingletonPresentation = !!(live && live.binding && !split && !activePresentation &&
@@ -1,4 +1,5 @@
1
1
  import { store } from './store.js';
2
+ import { selectBrowserPane } from './pane-browser-transport.js';
2
3
  import { livePaneBinding, paneMessageBinding } from './pane-binding.js';
3
4
  import { showToast } from './utils.js';
4
5
 
@@ -15,7 +16,7 @@ export function handlePaneBrowserMessage(event, host) {
15
16
  if (!s.connected || !pane || msg.projectSlug !== s.currentSlug || msg.projectSlug !== frame.dataset.projectSlug || String(msg.sessionId) !== frame.dataset.sessionId) return true;
16
17
  var expected = new URL('/p/' + encodeURIComponent(pane.slug) + '/?pane=1&session=' + encodeURIComponent(pane.sessionId), window.location.href).href;
17
18
  if (frame.clayPane ? !livePaneBinding(frame, msg) : frame.src !== expected) return true;
18
- // The authenticated host owns the browser; a live same-project pane supplies only a URL.
19
+ // Present in the host while browser effects retain the exact source pane binding.
19
20
  var binding = frame.clayPane && frame.clayPane.identity().binding, document = frame.contentDocument;
20
21
  function current() {
21
22
  var now = store.snap();
@@ -27,6 +28,9 @@ export function handlePaneBrowserMessage(event, host) {
27
28
  if (binding) return !!frame.clayPane && livePaneBinding(frame, msg) && frame.clayPane.identity().binding === binding;
28
29
  return frame.src === expected && !!(now.splitPanes && now.splitPanes.panes.some(function (item) { return String(item.slug) === pane.slug && String(item.sessionId) === String(pane.sessionId); }));
29
30
  }
30
- import('./shared-browser.js').then(function (module) { if (current()) module.openSharedBrowserAt(msg.url); }).catch(function (error) { showToast(error.message || 'Could not open the browser.', 'warn'); });
31
+ import('./shared-browser.js').then(function (module) {
32
+ if (!current() || binding && !selectBrowserPane(frame, binding)) return;
33
+ module.openSharedBrowserAt(msg.url);
34
+ }).catch(function (error) { showToast(error.message || 'Could not open the browser.', 'warn'); });
31
35
  return true;
32
36
  }
@@ -0,0 +1,110 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { livePaneBinding, paneMessageBinding, samePaneBinding, paneTarget } from './pane-binding.js';
4
+
5
+ // Browser-only presentation bridge. Effects still use the source pane's real
6
+ // authenticated socket and the existing server browser service.
7
+ function browserSource() { return store.get('paneBrowserSource') || null; }
8
+ function focusedOrigin() {
9
+ var focused = store.get('projectSplitViewFocusedOrigin') || store.get('projectSplitViewSingletonOrigin');
10
+ if (focused) return focused;
11
+ var view = (store.get('projectSplitViews') || []).find(function (item) { return item.id === store.get('projectSplitViewActiveId'); });
12
+ return view && view.activeSessionOriginId || store.get('sessionOriginId') || null;
13
+ }
14
+ var receive = null;
15
+ var changed = null;
16
+ var fieldsByAction = {
17
+ state_request: [], open: ['url', 'newTab', 'incognito'], view: ['browserId', 'visible'],
18
+ control: ['browserId', 'control'], close: ['browserId'], end: ['browserId'], input: ['browserId', 'epoch', 'event'],
19
+ };
20
+ function frames() {
21
+ return Array.from(document.querySelectorAll('#split-host .split-pane:not(.split-pane-cached) .split-pane-frame'));
22
+ }
23
+ export function hasBrowserPaneHost() { return !store.get('paneMode') && frames().some(function (frame) { return !!frame.clayPane || !!paneTarget(store.snap(), store.get('paneBindingSessions') || [], Number(frame.dataset.sessionId)); }); }
24
+ function current() {
25
+ var source = browserSource();
26
+ if (!source || source.socket !== getWs() || source.owner !== store.get('myUserId') || source.project !== store.get('currentSlug')) return false;
27
+ var frame = source.frame;
28
+ return frames().includes(frame) && frame.contentWindow === source.window && frame.contentDocument === source.document &&
29
+ !frame.dataset.requestedSessionId && livePaneBinding(frame, { paneBinding: source.binding });
30
+ }
31
+ export function browserPaneSessionId() { return current() ? browserSource().binding.sessionId : null; }
32
+ export function browserPaneStamp() { var source = browserSource(); return current() ? source.binding.requestId + ':' + source.binding.generation : null; }
33
+ export function browserPaneBound() { return !!browserSource(); }
34
+ export function selectBrowserPane(frame, binding) {
35
+ var source = browserSource();
36
+ if (!frame || !binding || !frames().includes(frame) || frame.dataset.requestedSessionId || !livePaneBinding(frame, { paneBinding: binding })) return false;
37
+ if (current() && source.frame === frame && samePaneBinding(source.binding, binding)) {
38
+ if (source.focusedOrigin !== focusedOrigin()) store.set({ paneBrowserSource: Object.assign({}, source, { focusedOrigin: focusedOrigin() }) });
39
+ return true;
40
+ }
41
+ var previous = store.get('sharedBrowser');
42
+ if (current() && previous) sendBrowserPaneCommand('view', { browserId: previous.id, visible: false });
43
+ store.set({ paneBrowserSource: { frame: frame, window: frame.contentWindow, document: frame.contentDocument, binding: binding,
44
+ socket: getWs(), owner: store.get('myUserId'), project: store.get('currentSlug'), focusedOrigin: focusedOrigin() }, paneBrowserTarget: binding });
45
+ if (changed) changed(true);
46
+ return true;
47
+ }
48
+ export function ensureBrowserPaneSource() {
49
+ if (current() && browserSource().focusedOrigin === focusedOrigin()) return true;
50
+ var candidates = frames();
51
+ var origin = focusedOrigin();
52
+ var frame = candidates.find(function (item) { return origin && item.dataset.sessionOriginId === origin; }) ||
53
+ candidates.find(function (item) { var pane = item.closest('.split-pane'); return pane && pane.dataset.active === 'true'; }) || candidates[0];
54
+ var live = frame && frame.clayPane && frame.clayPane.identity();
55
+ return !!live && selectBrowserPane(frame, live.binding);
56
+ }
57
+ export function sendBrowserPaneCommand(action, fields) {
58
+ var source = browserSource();
59
+ if (!Object.prototype.hasOwnProperty.call(fieldsByAction, action)) return false;
60
+ if (!source && !hasBrowserPaneHost()) return null;
61
+ if (!current() || !source.frame.clayPane || typeof source.frame.clayPane.browserCommand !== 'function') return false;
62
+ return source.frame.clayPane.browserCommand(action, fields || {}, source.binding);
63
+ }
64
+ export function sendBoundBrowserCommand(action, fields, binding) {
65
+ var allowed = Object.prototype.hasOwnProperty.call(fieldsByAction, action) && fieldsByAction[action], ws = getWs();
66
+ if (!allowed || !ws || ws.readyState !== 1 || !samePaneBinding(binding, paneMessageBinding()) ||
67
+ !store.get('connected') || store.get('panePendingBinding') || store.get('activeSessionId') !== binding.sessionId) return false;
68
+ var message = { type: 'shared_browser_' + action, sessionId: binding.sessionId, paneBinding: binding };
69
+ allowed.forEach(function (key) { if (Object.prototype.hasOwnProperty.call(fields, key)) message[key] = fields[key]; });
70
+ ws.send(JSON.stringify(message));
71
+ return true;
72
+ }
73
+ export function forwardBrowserPaneMessage(message) {
74
+ var frame = window.frameElement;
75
+ if (!store.get('paneMode') || !frame || !frame.clayPane) return false;
76
+ if (['shared_browser_state', 'shared_browser_frame', 'shared_browser_error'].indexOf(message.type) < 0) return false;
77
+ var binding = paneMessageBinding();
78
+ if (binding && samePaneBinding(binding, message.paneActivation)) {
79
+ window.parent.postMessage({ type: 'clay-pane-browser-update', paneBinding: binding, message: message }, location.origin);
80
+ }
81
+ return true;
82
+ }
83
+ export function initBrowserPaneTransport(onMessage, onChanged) {
84
+ if (store.get('paneMode')) return;
85
+ receive = onMessage; changed = onChanged;
86
+ window.addEventListener('message', function (event) {
87
+ var data = event.data;
88
+ if (event.origin !== location.origin || !data || data.type !== 'clay-pane-browser-update' || !data.message) return;
89
+ var frame = frames().find(function (item) { return item.contentWindow === event.source; });
90
+ var message = data.message, source = browserSource();
91
+ if (!frame || !livePaneBinding(frame, data) || !samePaneBinding(data.paneBinding, message.paneActivation) ||
92
+ message.sessionId !== data.paneBinding.sessionId || ['shared_browser_state', 'shared_browser_frame', 'shared_browser_error'].indexOf(message.type) < 0) return;
93
+ if (!current() || source.frame !== frame || !samePaneBinding(source.binding, data.paneBinding)) {
94
+ if (message.type !== 'shared_browser_state' || !message.present || !selectBrowserPane(frame, data.paneBinding)) return;
95
+ }
96
+ receive(message, true);
97
+ });
98
+ document.addEventListener('clay-pane-ready', function () {
99
+ queueMicrotask(function () {
100
+ if (!current() && ensureBrowserPaneSource()) sendBrowserPaneCommand('state_request', {});
101
+ });
102
+ }, true);
103
+ store.subscribe(function () {
104
+ var source = browserSource();
105
+ if (source && !current()) {
106
+ store.set({ paneBrowserSource: null, paneBrowserTarget: null });
107
+ if (changed) changed(false);
108
+ }
109
+ });
110
+ }