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
@@ -8,8 +8,12 @@ function state() { return store.get('contextualClayGeometry') || {}; }
8
8
  function surface() { return document.getElementById('contextual-clay'); }
9
9
  function narrow() { return window.matchMedia('(max-width: 768px)').matches; }
10
10
  function rectValue(rect) { return { left: rect.left, top: rect.top, width: rect.width, height: rect.height }; }
11
+ function maximumWidth() {
12
+ var style = getComputedStyle(surface());
13
+ return (parseFloat(style.getPropertyValue('--mate-content-width')) || 860) + (parseFloat(style.getPropertyValue('--contextual-chat-gutter')) || 80);
14
+ }
11
15
  function bounded(value) {
12
- return { left: Math.round(Math.max(0, Math.min(16384, value.left))), top: Math.round(Math.max(0, Math.min(16384, value.top))), width: Math.round(Math.max(340, Math.min(2560, value.width))), height: Math.round(Math.max(360, Math.min(2160, value.height))) };
16
+ return { left: Math.round(Math.max(0, Math.min(16384, value.left))), top: Math.round(Math.max(0, Math.min(16384, value.top))), width: Math.round(Math.max(340, Math.min(maximumWidth(), value.width))), height: Math.round(Math.max(360, Math.min(2160, value.height))) };
13
17
  }
14
18
  function setState(patch) { store.set({ contextualClayGeometry: Object.assign({}, state(), patch) }); }
15
19
  function defaultGeometry() {
@@ -22,7 +26,7 @@ export function positionContextualClay() {
22
26
  var hint = renderContextualMateHint();
23
27
  if (!element || narrow() || (store.get('contextualClay') || {}).mode === 'opening') return;
24
28
  var desired = state().preview || state().desired || defaultGeometry();
25
- var width = Math.min(desired.width, window.innerWidth - 16);
29
+ var width = Math.min(desired.width, maximumWidth(), window.innerWidth - 16);
26
30
  var height = Math.min(desired.height, window.innerHeight - 16);
27
31
  var left = Math.max(8, Math.min(desired.left, window.innerWidth - width - 8));
28
32
  var top = Math.max(8, Math.min(desired.top, window.innerHeight - height - 8));
@@ -78,7 +82,7 @@ function move(event) {
78
82
  if (!state().preview && Math.hypot(dx, dy) < 3) return;
79
83
  var value = Object.assign({}, drag.start);
80
84
  if (drag.edge) {
81
- if (drag.edge.indexOf('e') >= 0) value.width = Math.max(340, Math.min(2560, window.innerWidth - value.left - 8, value.width + dx));
85
+ if (drag.edge.indexOf('e') >= 0) value.width = Math.max(340, Math.min(maximumWidth(), window.innerWidth - value.left - 8, value.width + dx));
82
86
  if (drag.edge.indexOf('s') >= 0) value.height = Math.max(360, Math.min(2160, window.innerHeight - value.top - 8, value.height + dy));
83
87
  } else {
84
88
  value.left = Math.max(8, Math.min(window.innerWidth - value.width - 8, value.left + dx));
@@ -0,0 +1,12 @@
1
+ // Session tool input is historical evidence. The current disk file is not.
2
+ export function historicalSnapshot(entry, before) {
3
+ if (!entry || entry.source === 'git') return undefined;
4
+ var full = before ? entry.beforeContent : entry.afterContent;
5
+ if (typeof full === 'string') return full;
6
+ if (entry.toolName === 'Write' || entry.isFullWrite) return before ? undefined : typeof entry.content === 'string' ? entry.content : undefined;
7
+ var excerpt = before ? entry.old_string : entry.new_string;
8
+ return typeof excerpt === 'string' ? excerpt : undefined;
9
+ }
10
+ export function historicalScope(entry) {
11
+ return entry && entry.source !== 'git' && entry.toolName === 'Edit' && typeof entry.afterContent !== 'string' ? 'Recorded changed excerpt; a full-file snapshot was not recorded.' : '';
12
+ }
@@ -0,0 +1,55 @@
1
+ import { recordToolExpansion } from './view-presentation-state.js';
2
+ import { registerStudioPaint } from './clay-studio-render.js';
3
+ import { bindFileReview } from './file-pane-review.js';
4
+ // Compatibility entrypoints resolve an actual pane in the authenticated current View.
5
+ import { store } from './store.js';
6
+ import { activeSavedView } from './view-workspace-state.js';
7
+ import { filePaneStates, filePaneState, openIndependentFile, updateFilePaneState, repaintFilePane, actOnFilePane, closeFilePane, mountFilePane } from './file-pane-instances.js';
8
+ import { claimRightWorkbench } from './right-workbench.js';
9
+ import { presentedDocument } from './clay-studio-document.js';
10
+ import { showToast } from './utils.js';
11
+
12
+ export function independentFilesActive() { return !!activeSavedView() && !store.get('paneMode'); }
13
+ export function fileEntryContext() { var s = store.snap(); return JSON.stringify([s.currentSlug, s.myUserId, s.currentProjectOwnerId, s.projectSplitViewActiveId, s.projectSplitViewSocketGeneration]); }
14
+ export function normalizedFilePath(path) {
15
+ var cwd = String(store.get('cwd') || '').replace(/\/$/, '');
16
+ return cwd && path.indexOf(cwd + '/') === 0 ? path.slice(cwd.length + 1) : path;
17
+ }
18
+ export function activeFilePane() {
19
+ var id = store.get('activeFilePaneId'), state = filePaneState(id);
20
+ if (state) return state;
21
+ var ids = Object.keys(filePaneStates()).reverse();
22
+ for (var i = 0; i < ids.length; i++) { state = filePaneState(ids[i]); if (state) return state; }
23
+ return null;
24
+ }
25
+ export function closeActiveFilePane() { var state = activeFilePane(); if (state) closeFilePane(state.instanceId); }
26
+ export function expandActiveFilePane(enabled) { var state = activeFilePane(); if (state) { updateFilePaneState(state.instanceId, { expanded: !!enabled }); repaintFilePane(state.instanceId); recordToolExpansion('file-' + state.instanceId, enabled); } }
27
+ export function reopenIndependentFile() {
28
+ var last = filePaneStates()[store.get('activeFilePaneId')];
29
+ if (last && !last.open && last.context === fileEntryContext()) { mountFilePane(last); return true; }
30
+ var state = activeFilePane();
31
+ if (state) { claimRightWorkbench('file-' + state.instanceId); return true; }
32
+ var closed = Object.keys(filePaneStates()).reverse().map(function (id) { return filePaneStates()[id]; }).find(function (item) { return item.context === fileEntryContext(); });
33
+ if (!closed) return false;
34
+ mountFilePane(closed); return true;
35
+ }
36
+ export function refreshIndependentFile(path) {
37
+ path = normalizedFilePath(path);
38
+ Object.keys(filePaneStates()).forEach(function (id) { var state = filePaneState(id); if (state && state.resource.path === path && !state.historyVisible && !state.diff) actOnFilePane(id, 'refresh'); });
39
+ }
40
+ export function presentIndependentFile(msg, diff, review) {
41
+ // Source-frame receipts do not grant access to a same-named parent-project file.
42
+ var source = presentedDocument(msg.path);
43
+ if (source && source.descriptor.sourceSlug !== store.get('currentSlug')) { showToast('Source-project document presentation is unavailable in this View.', 'warn'); return false; }
44
+ if (!openIndependentFile(msg.path, { rendered: !diff, diff: diff })) return false;
45
+ var path = normalizedFilePath(msg.path);
46
+ Object.keys(filePaneStates()).forEach(function (id) {
47
+ var state = filePaneState(id);
48
+ if (!state || state.resource.mode !== 'follow' || state.resource.path !== path) return;
49
+ bindFileReview(state, review);
50
+ updateFilePaneState(id, { suppliedSnapshotRequestId: state.requestId, content: msg.content || '', size: msg.size || (msg.content || '').length, binary: !!msg.binary, diff: diff || null }); repaintFilePane(id);
51
+ var panel = document.getElementById('file-' + id);
52
+ registerStudioPaint(msg, panel.querySelector('.tool-pane-header'), null, panel.querySelector('.file-viewer-body'));
53
+ });
54
+ return true;
55
+ }
@@ -0,0 +1,78 @@
1
+ import { historicalSnapshot, historicalScope } from './file-history-snapshot.js';
2
+ import { filePaneState, updateFilePaneState, sendFilePaneRequest, repaintFilePane } from './file-pane-instances.js';
3
+ import { renderSplitDiff, renderUnifiedDiff, reconstructPatchSources } from './diff.js';
4
+ import { renderCodeWithLineNumbers, mapExtToLanguage } from './file-viewer-source.js';
5
+ import { navigateToEdit } from './filebrowser.js';
6
+
7
+ function control(label, action) { var b = document.createElement('button'); b.type = 'button'; b.textContent = label; b.addEventListener('click', action); return b; }
8
+ function current(state) { var live = filePaneState(state.instanceId); return live && live.requestId === state.requestId; }
9
+ function change(state, patch) { if (!current(state)) return; updateFilePaneState(state.instanceId, patch); repaintFilePane(state.instanceId); }
10
+ function entryContent(state, entry, before) { return entry.source === 'git' ? (state.historyContents || {})[entry.hash] : historicalSnapshot(entry, before); }
11
+ function requestEntry(state, entry) { if (entry.source === 'git') sendFilePaneRequest(state, 'fs_file_at', { hash: entry.hash }); }
12
+ export function paintFileHistory(body, state) {
13
+ body.replaceChildren();
14
+ var target = state.historyTarget;
15
+ if (target) {
16
+ var bar = document.createElement('div'); bar.className = 'file-history-view-bar';
17
+ bar.appendChild(control('Back to timeline', function () { change(state, { historyTarget: null }); }));
18
+ ['Split', 'Unified', 'Source'].forEach(function (mode) { bar.appendChild(control(mode, function () { change(state, { historyMode: mode.toLowerCase() }); })); });
19
+ body.appendChild(bar);
20
+ var content = document.createElement('div'); body.appendChild(content);
21
+ var before, after;
22
+ if (target.kind === 'git') {
23
+ if (state.historyDiffHash !== target.entry.hash) { content.textContent = 'Loading revision…'; return; }
24
+ if (state.historyError) { content.textContent = state.historyError; return; }
25
+ var parsed = reconstructPatchSources(state.historyDiff || ''); before = parsed.oldStr; after = parsed.newStr;
26
+ if (state.historyMode === 'source') after = entryContent(state, target.entry);
27
+ } else if (target.kind === 'compare') { before = entryContent(state, target.entries[0], true); after = entryContent(state, target.entries[1]); }
28
+ else { before = historicalSnapshot(target.entry, true); after = historicalSnapshot(target.entry, false); }
29
+ var entries = target.entries || [target.entry], historical = entries.some(function (entry) { return entry.source !== 'git'; });
30
+ var note = entries.map(historicalScope).filter(Boolean).join(' ');
31
+ if (note) { var notice = document.createElement('p'); notice.textContent = note; body.insertBefore(notice, content); }
32
+ if (after === undefined || before === undefined && state.historyMode !== 'source') {
33
+ content.textContent = state.historyError || (historical ? 'This historical snapshot was not recorded. Use Source to inspect the recorded content, when available.' : 'Loading revision…'); return;
34
+ }
35
+ var ext = state.resource.path.split('.').pop();
36
+ if (state.historyMode === 'source') renderCodeWithLineNumbers(content, after, ext);
37
+ else content.appendChild((state.historyMode === 'unified' ? renderUnifiedDiff : renderSplitDiff)(before, after, mapExtToLanguage(ext)));
38
+ return;
39
+ }
40
+ if (!state.entries) { body.textContent = 'Loading history…'; return; }
41
+ if (!state.entries.length) { body.textContent = 'No file history'; return; }
42
+ var selected = state.historySelected || [], comparison = document.createElement('div'); comparison.className = 'file-history-view-bar';
43
+ comparison.textContent = 'Compare revisions (' + selected.length + '/2)';
44
+ var compare = control('Compare', function () {
45
+ if (!current(state) || selected.length !== 2) return;
46
+ change(state, { historyTarget: { kind: 'compare', entries: selected }, historyError: null });
47
+ selected.forEach(function (entry) { requestEntry(state, entry); });
48
+ }); compare.disabled = selected.length !== 2; comparison.appendChild(compare); body.appendChild(comparison);
49
+ state.entries.forEach(function (entry) {
50
+ var row = document.createElement('div'); row.className = 'file-history-entry';
51
+ var title = document.createElement('div'); title.className = 'file-history-title'; title.textContent = entry.message || entry.assistantSnippet || entry.toolName || 'Edit'; row.appendChild(title);
52
+ var meta = document.createElement('div'); meta.className = 'file-history-meta'; meta.textContent = entry.source === 'git' ? entry.hash.slice(0, 7) + ' · ' + (entry.author || '') : entry.sessionTitle || 'Conversation'; row.appendChild(meta);
53
+ if (entry.source !== 'git' && entry.toolName === 'Edit') row.appendChild(renderUnifiedDiff(entry.old_string || '', entry.new_string || '', mapExtToLanguage(state.resource.path.split('.').pop())));
54
+ var select = control(selected.indexOf(entry) >= 0 ? 'Deselect' : 'Select for comparison', function () {
55
+ var next = selected.filter(function (item) { return item !== entry; });
56
+ if (next.length === selected.length) next = next.slice(-1).concat(entry);
57
+ change(state, { historySelected: next });
58
+ }); row.appendChild(select);
59
+ row.appendChild(control('View change', function () {
60
+ change(state, { historyTarget: { kind: entry.source === 'git' ? 'git' : 'session', entry: entry }, historyError: null });
61
+ if (entry.source === 'git') { sendFilePaneRequest(state, 'fs_git_diff', { hash: entry.hash }); requestEntry(state, entry); }
62
+ }));
63
+ if (entry.source === 'session') row.appendChild(control('Go to conversation', function () { if (current(state)) navigateToEdit(entry); }));
64
+ body.appendChild(row);
65
+ });
66
+ }
67
+ export function receiveFileHistory(state, msg) {
68
+ if (msg.type === 'fs_file_history_result') updateFilePaneState(state.instanceId, { entries: msg.entries || [], historyError: msg.error || null });
69
+ else if (msg.type === 'fs_git_diff_result') {
70
+ var target = state.historyTarget;
71
+ if (!target || target.kind !== 'git' || target.entry.hash !== msg.hash) return;
72
+ updateFilePaneState(state.instanceId, { historyDiff: msg.diff, historyDiffHash: msg.hash, historyError: msg.error || null });
73
+ } else if (msg.type === 'fs_file_at_result') {
74
+ var contents = Object.assign({}, state.historyContents || {}); contents[msg.hash] = msg.content;
75
+ updateFilePaneState(state.instanceId, { historyContents: contents, historyError: msg.error || null });
76
+ } else return;
77
+ if (state.historyVisible) repaintFilePane(state.instanceId);
78
+ }
@@ -0,0 +1,140 @@
1
+ import { recordToolExpansion } from './view-presentation-state.js';
2
+ import { releaseFileReview } from './file-pane-review.js';
3
+ import { paintFileHistory, receiveFileHistory } from './file-pane-history.js';
4
+ import { store } from './store.js';
5
+ import { getWs } from './ws-ref.js';
6
+ import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench, unregisterRightWorkbench } from './right-workbench.js';
7
+ import { TOOL_PANES } from './tool-pane-tree.js';
8
+ import { workspaceChanged, freshInstance, activeSavedView } from './view-workspace-state.js';
9
+ import { filePaneShell, updateFilePaneToolbar } from './file-pane-toolbar.js';
10
+ import { paintFilePane, filePreviewKind } from './file-pane-render.js';
11
+ import { cancelPlantUmlPreview } from './plantuml-preview.js';
12
+ import { copyToClipboard, showToast } from './utils.js';
13
+ import { copyMarkdownFormatting } from './rich-clipboard.js';
14
+ import { exportMarkdownAsPdf } from './markdown.js';
15
+ import { downloadProjectFile } from './filebrowser-context-menu.js';
16
+
17
+ export function filePaneStates() { return store.get('filePaneInstances') || {}; }
18
+ function context() { var s = store.snap(); return JSON.stringify([s.currentSlug, s.myUserId, s.currentProjectOwnerId, s.projectSplitViewActiveId, s.projectSplitViewSocketGeneration]); }
19
+ export function filePaneState(id) { var state = filePaneStates()[id]; return state && state.context === context() && state.open ? state : null; }
20
+ export function updateFilePaneState(id, patch) {
21
+ var states = Object.assign({}, filePaneStates()); states[id] = Object.assign({}, states[id], patch); store.set({ filePaneInstances: states }); return states[id];
22
+ }
23
+ export function sendFilePaneRequest(state, type, extra) {
24
+ var ws = getWs();
25
+ if (!ws || !store.get('connected') || !filePaneState(state.instanceId)) return;
26
+ ws.send(JSON.stringify(Object.assign({ type: type, instanceId: state.instanceId, requestId: state.requestId, path: state.resource.path,
27
+ projectSlug: store.get('currentSlug'), accountId: store.get('myUserId') || null }, extra)));
28
+ }
29
+ function unwatch(state) {
30
+ var ws = getWs();
31
+ if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'fs_unwatch', instanceId: state.instanceId, requestId: state.requestId }));
32
+ }
33
+ function request(id) {
34
+ var state = filePaneState(id); if (!state) return;
35
+ unwatch(state); state = updateFilePaneState(id, { requestId: freshInstance(), error: null, suppliedSnapshotRequestId: null });
36
+ sendFilePaneRequest(state, 'fs_read'); sendFilePaneRequest(state, 'fs_watch'); sendFilePaneRequest(state, 'fs_file_history');
37
+ }
38
+ function panelFor(id) { return document.getElementById('file-' + id); }
39
+ export function repaintFilePane(id) {
40
+ var state = filePaneState(id), panel = panelFor(id); if (!state || !panel) return;
41
+ delete panel.querySelector('.tool-pane-header').dataset.controlSurface;
42
+ updateFilePaneToolbar(panel, state);
43
+ panel.classList.toggle('panel-fullscreen', !!state.expanded);
44
+ var body = panel.querySelector('.file-viewer-body');
45
+ if (state.historyVisible) { paintFileHistory(body, state); return; }
46
+ paintFilePane(body, state);
47
+ }
48
+ export function closeFilePane(id) {
49
+ var state = filePaneState(id); if (!state) return;
50
+ recordToolExpansion('file-' + id, false);
51
+ releaseFileReview(id); unwatch(state); cancelPlantUmlPreview(id); updateFilePaneState(id, { open: false, requestId: null });
52
+ var panel = panelFor(id); if (panel) { panel.dispatchEvent(new Event('clay-tool-pane-dispose')); panel.remove(); }
53
+ releaseRightWorkbench('file-' + id); unregisterRightWorkbench('file-' + id); delete TOOL_PANES['file-' + id]; workspaceChanged();
54
+ }
55
+ export function actOnFilePane(id, action) {
56
+ var state = filePaneState(id); if (!state) return;
57
+ if (action !== 'refresh') store.set({ activeFilePaneId: id });
58
+ var resource = Object.assign({}, state.resource), patch = {};
59
+ if (action === 'close') { closeFilePane(id); return; }
60
+ if (action === 'pin') resource.mode = resource.mode === 'pin' ? 'follow' : 'pin';
61
+ else if (action === 'preview' || action === 'source') { resource.rendered = action === 'preview'; patch.historyVisible = false; patch.diff = null; }
62
+ else if (action === 'history') patch.historyVisible = !state.historyVisible;
63
+ else if (action === 'expand') patch.expanded = !state.expanded;
64
+ else if (action === 'escape') { patch.expanded = false; patch.presenting = false; }
65
+ else if (action === 'refresh') { request(id); return; }
66
+ else if (action === 'copy') { copyToClipboard(state.diff ? state.diff.newStr || '' : state.content || ''); return; }
67
+ else if (action === 'formatted') { copyMarkdownFormatting(state.content || '').catch(function (error) { showToast(error.message, 'error'); }); return; }
68
+ else if (action === 'download') { downloadProjectFile(resource.path); return; }
69
+ else if (action === 'pdf') {
70
+ resource.rendered = true; updateFilePaneState(id, { resource: resource, historyVisible: false, presenting: false }); repaintFilePane(id);
71
+ var markdown = panelFor(id).querySelector('.file-viewer-markdown');
72
+ if (markdown) exportMarkdownAsPdf(markdown, resource.path).catch(function (error) { showToast(error.message, 'error'); });
73
+ workspaceChanged(); return;
74
+ } else if (/^h[123]$/.test(action)) patch.slideLevel = Number(action.slice(1));
75
+ else if (action === 'slides') { patch.presenting = !state.presenting; patch.expanded = patch.presenting; patch.slideIndex = 0; resource.rendered = true; patch.historyVisible = false; }
76
+ else if (action === 'next' || action === 'previous') {
77
+ if (!state.presenting) return;
78
+ var count = (state.content || '').split('\n').filter(function (line) { return new RegExp('^' + '#'.repeat(state.slideLevel || 1) + ' ').test(line); }).length;
79
+ patch.slideIndex = Math.max(0, Math.min(Math.max(0, count - 1), (state.slideIndex || 0) + (action === 'next' ? 1 : -1)));
80
+ } else return;
81
+ patch.resource = resource; updateFilePaneState(id, patch); repaintFilePane(id);
82
+ if (patch.expanded !== undefined) recordToolExpansion('file-' + id, patch.expanded);
83
+ workspaceChanged();
84
+ }
85
+ export function mountFilePane(pane) {
86
+ var id = pane.instanceId, name = 'file-' + id;
87
+ if (!filePaneState(id)) {
88
+ updateFilePaneState(id, { instanceId: id, context: context(), open: true, resource: Object.assign({}, pane.resource), content: null, requestId: freshInstance() });
89
+ TOOL_PANES[name] = { id: name, label: 'Files', icon: 'file-text' };
90
+ var panel = filePaneShell(id, function (action) { actOnFilePane(id, action); });
91
+ function activate() { store.set({ activeFilePaneId: id }); }
92
+ panel.addEventListener('pointerdown', activate, true); panel.addEventListener('focusin', activate);
93
+ document.getElementById('main-panels').appendChild(panel);
94
+ registerRightWorkbench(name, function () { closeFilePane(id); }, name);
95
+ }
96
+ store.set({ activeFilePaneId: id });
97
+ claimRightWorkbench(name); repaintFilePane(id); request(id); workspaceChanged(); return 'restored';
98
+ }
99
+ export function openIndependentFile(path, options) {
100
+ options = options || {};
101
+ if (!store.get('connected') || options.projectSlug && options.projectSlug !== store.get('currentSlug') || options.sessionId && String(options.sessionId) !== String(store.get('activeSessionId'))) return false;
102
+ var cwd = String(store.get('cwd') || '').replace(/\/$/, '');
103
+ if (cwd && path.indexOf(cwd + '/') === 0) path = path.slice(cwd.length + 1);
104
+ if (!path || /[\\\x00-\x1f\x7f:]/.test(path) || !path.split('/').every(function (part) { return part && part !== '.' && part !== '..'; })) { showToast('This file cannot be bound to the current View.', 'warn'); return false; }
105
+ var states = filePaneStates(), following = Object.keys(states).map(function (id) { return filePaneState(id); }).filter(function (state) { return state && state.resource.mode === 'follow'; });
106
+ if (following.length) {
107
+ following.forEach(function (state) {
108
+ store.set({ activeFilePaneId: state.instanceId });
109
+ releaseFileReview(state.instanceId); unwatch(state); updateFilePaneState(state.instanceId, { content: null, entries: null, historyVisible: false, historyTarget: null, historyContents: {}, historySelected: [], historyDiff: null, historyDiffHash: null, diff: options.diff || null, line: options.line || null, heading: options.heading || null,
110
+ resource: { path: path, mode: 'follow', rendered: !options.line && !options.diff && options.rendered !== false && !!filePreviewKind(path) } });
111
+ repaintFilePane(state.instanceId); request(state.instanceId);
112
+ }); workspaceChanged(); return true;
113
+ }
114
+ var view = activeSavedView(), count = view ? view.workspace.panes.filter(function (pane) { return pane.kind !== 'session' && pane.open; }).length : Object.keys(states).filter(function (id) { return filePaneState(id); }).length;
115
+ if (count >= 8) { showToast('A View supports at most eight tool panes. Close a pane first.', 'warn'); return false; }
116
+ var id = freshInstance();
117
+ mountFilePane({ instanceId: id, resource: { path: path, mode: 'follow', rendered: !options.line && !options.diff && options.rendered !== false && !!filePreviewKind(path) } });
118
+ updateFilePaneState(id, { line: options.line || null, heading: options.heading || null, diff: options.diff || null }); return true;
119
+ }
120
+ export function routeFilePaneMessage(msg) {
121
+ if (!msg.instanceId) return false;
122
+ var state = filePaneState(msg.instanceId);
123
+ if (!state || !store.get('connected') || state.requestId !== msg.requestId || state.resource.path !== msg.path) return true;
124
+ if (msg.type === 'fs_read_result' && (msg.projectSlug !== store.get('currentSlug') || (msg.accountId || null) !== (store.get('myUserId') || null))) return true;
125
+ if (msg.type === 'fs_read_result' && !msg.error && state.suppliedSnapshotRequestId === msg.requestId) return true;
126
+ if (msg.type === 'fs_read_result' || msg.type === 'fs_file_changed') {
127
+ updateFilePaneState(state.instanceId, { content: msg.content, size: msg.size, binary: msg.binary, imageUrl: msg.imageUrl, error: msg.error || null }); repaintFilePane(state.instanceId);
128
+ } else receiveFileHistory(state, msg);
129
+ return true;
130
+ }
131
+ export function resetFilePanes() {
132
+ Object.keys(filePaneStates()).forEach(function (id) {
133
+ releaseFileReview(id); var state = filePaneStates()[id]; unwatch(state); cancelPlantUmlPreview(id);
134
+ var panel = panelFor(id); if (panel) { panel.dispatchEvent(new Event('clay-tool-pane-dispose')); panel.remove(); }
135
+ unregisterRightWorkbench('file-' + id); delete TOOL_PANES['file-' + id];
136
+ }); store.set({ filePaneInstances: {}, activeFilePaneId: null });
137
+ }
138
+ store.subscribe(function (state, previous) {
139
+ if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.currentProjectOwnerId !== previous.currentProjectOwnerId || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration) resetFilePanes();
140
+ });
@@ -0,0 +1,41 @@
1
+ import { filePaneState, openIndependentFile } from './file-pane-instances.js';
2
+ import { localFileTarget } from './clay-file-links.js';
3
+
4
+ export function resolvePaneLink(base, target) {
5
+ if (target.charAt(0) === '/') return target;
6
+ var parts = base.split('/'); parts.pop();
7
+ var segments = target.split('/');
8
+ for (var i = 0; i < segments.length; i++) {
9
+ if (!segments[i] || segments[i] === '.') continue;
10
+ if (segments[i] === '..') { if (!parts.length) return null; parts.pop(); }
11
+ else parts.push(segments[i]);
12
+ }
13
+ return parts.join('/');
14
+ }
15
+ export function bindFilePaneLinks(markdown, state) {
16
+ var counts = {};
17
+ Array.from(markdown.querySelectorAll('h1,h2,h3,h4,h5,h6')).forEach(function (heading) {
18
+ var slug = heading.textContent.trim().toLowerCase().replace(/[^\p{L}\p{N}_\s-]/gu, '').replace(/\s+/g, '-');
19
+ var count = counts[slug] || 0; counts[slug] = count + 1; heading.dataset.fileHeading = slug + (count ? '-' + count : '');
20
+ });
21
+ markdown.addEventListener('click', function (event) {
22
+ var link = event.target.closest('a, .clay-file-link'); if (!link || !markdown.contains(link)) return;
23
+ var href = link.getAttribute('href'), path = link.dataset.filePath;
24
+ if (href && href.charAt(0) === '#') {
25
+ event.preventDefault(); event.stopPropagation();
26
+ var fragment; try { fragment = decodeURIComponent(href.slice(1)); } catch (e) { return; }
27
+ Array.from(markdown.querySelectorAll('[data-file-heading]')).some(function (heading) { if (heading.dataset.fileHeading !== fragment) return false; heading.scrollIntoView({ block: 'start' }); return true; }); return;
28
+ }
29
+ var target = path ? { path: path, line: Number(link.dataset.fileLine) || null } : localFileTarget(href);
30
+ if (!target) return;
31
+ event.preventDefault(); event.stopPropagation();
32
+ var live = filePaneState(state.instanceId); if (!live || live.requestId !== state.requestId) return;
33
+ var resolved = resolvePaneLink(state.resource.path, target.path);
34
+ if (resolved) openIndependentFile(resolved, { line: target.line, heading: link.dataset.fileHeading || null });
35
+ });
36
+ }
37
+
38
+ export function scrollFileHeading(markdown, heading) {
39
+ if (!heading) return;
40
+ Array.from(markdown.querySelectorAll('[data-file-heading]')).some(function (element) { if (!/^H[1-6]$/.test(element.tagName) || element.dataset.fileHeading !== heading) return false; element.scrollIntoView({ block: 'start' }); return true; });
41
+ }
@@ -0,0 +1,48 @@
1
+ import { bindFilePaneLinks, scrollFileHeading } from './file-pane-links.js';
2
+ import { paintFileReview } from './file-pane-review.js';
3
+ import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
4
+ import { renderHtmlPreview, isHtmlFile } from './html-preview.js';
5
+ import { renderPlantUmlPreview, cancelPlantUmlPreview, isPlantUmlFile } from './plantuml-preview.js';
6
+ import { renderCodeWithLineNumbers, renderLargeTextFile, mapExtToLanguage } from './file-viewer-source.js';
7
+
8
+ export function filePreviewKind(path) {
9
+ return /\.mdx?$/i.test(path) ? 'markdown' : isHtmlFile(path) ? 'html' : isPlantUmlFile(path) ? 'uml' : /\.svg$/i.test(path) ? 'svg' : null;
10
+ }
11
+ export function paintFilePane(body, state) {
12
+ cancelPlantUmlPreview(state.instanceId);
13
+ var path = state.resource.path, content = state.content || '', kind = filePreviewKind(path), scroll = body.scrollTop;
14
+ body.replaceChildren();
15
+ if (state.error) { var error = document.createElement('div'); error.className = 'file-tree-error'; error.textContent = state.error; body.appendChild(error); return; }
16
+ if (state.content == null && !state.binary) { body.textContent = 'Loading file…'; return; }
17
+ if (state.binary) {
18
+ if (state.imageUrl) { var image = document.createElement('img'); image.src = state.imageUrl; image.alt = path; body.appendChild(image); }
19
+ else body.textContent = 'Binary file';
20
+ } else if (state.diff) {
21
+ paintFileReview(body, state);
22
+ } else if ((state.size || content.length) > 1024 * 1024) renderLargeTextFile(body, content, state.size);
23
+ else if (!state.resource.rendered || !kind) renderCodeWithLineNumbers(body, content, path.split('.').pop());
24
+ else if (kind === 'markdown') {
25
+ var markdown = document.createElement('div'); markdown.className = 'file-viewer-markdown';
26
+ markdown.innerHTML = renderMarkdown(content); body.appendChild(markdown);
27
+ Array.from(markdown.querySelectorAll('img')).forEach(function (image) {
28
+ var src = image.getAttribute('src');
29
+ if (src && !/^(https?:|data:|api\/file)/.test(src)) image.src = 'api/file?path=' + encodeURIComponent(path.replace(/[^/]*$/, '') + src);
30
+ });
31
+ bindFilePaneLinks(markdown, state);
32
+ highlightCodeBlocks(markdown); renderMermaidBlocks(markdown);
33
+ if (state.presenting) {
34
+ var slide = -1, level = 'H' + (state.slideLevel || 1);
35
+ Array.from(markdown.children).forEach(function (child) {
36
+ if (child.tagName === level) slide++;
37
+ child.hidden = Math.max(0, slide) !== (state.slideIndex || 0);
38
+ });
39
+ }
40
+ } else if (kind === 'html') renderHtmlPreview(body, content, path);
41
+ else if (kind === 'uml') renderPlantUmlPreview(body, content, path, state.instanceId);
42
+ else {
43
+ var svg = document.createElement('img'); svg.className = 'file-viewer-svg-preview'; svg.alt = path;
44
+ svg.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(content); body.appendChild(svg);
45
+ }
46
+ body.scrollTop = state.line ? Math.max(0, (state.line - 1) * 20 - 80) : scroll;
47
+ if (kind === 'markdown' && state.heading) scrollFileHeading(body, state.heading);
48
+ }
@@ -0,0 +1,26 @@
1
+ import { filePaneState, updateFilePaneState, repaintFilePane } from './file-pane-instances.js';
2
+ import { renderSplitDiff, renderUnifiedDiff } from './diff.js';
3
+ import { renderCodeWithLineNumbers, mapExtToLanguage } from './file-viewer-source.js';
4
+
5
+ var callbacks = new Map();
6
+ export function bindFileReview(state, review) {
7
+ if (review) callbacks.set(state.instanceId, { requestId: state.requestId, context: state.context, review: review });
8
+ else callbacks.delete(state.instanceId);
9
+ }
10
+ export function releaseFileReview(id) { callbacks.delete(id); }
11
+ export function paintFileReview(body, state) {
12
+ var bar = document.createElement('div'); bar.className = 'file-history-view-bar';
13
+ var receipt = callbacks.get(state.instanceId);
14
+ function current() { var live = filePaneState(state.instanceId); return live && live.requestId === state.requestId && live.context === state.context; }
15
+ function button(label, callback) { var b = document.createElement('button'); b.type = 'button'; b.textContent = label; b.disabled = !callback; b.addEventListener('click', function () { if (current() && callback) callback(); }); bar.appendChild(b); }
16
+ button('Back to file', function () { updateFilePaneState(state.instanceId, { diff: null }); repaintFilePane(state.instanceId); });
17
+ ['Split', 'Unified', 'Source'].forEach(function (mode) { button(mode, function () { updateFilePaneState(state.instanceId, { reviewMode: mode.toLowerCase() }); repaintFilePane(state.instanceId); }); });
18
+ if (receipt && receipt.requestId === state.requestId && receipt.context === state.context) {
19
+ var review = receipt.review, label = document.createElement('span'); label.textContent = (review.index + 1) + ' of ' + review.total; bar.appendChild(label);
20
+ button('Previous file', review.previous); button('Next file', review.next); if (review.askAgent) button('Ask agent', review.askAgent);
21
+ }
22
+ body.appendChild(bar);
23
+ var content = document.createElement('div'), ext = state.resource.path.split('.').pop(); body.appendChild(content);
24
+ if (state.reviewMode === 'source') renderCodeWithLineNumbers(content, state.diff.newStr || '', ext);
25
+ else content.appendChild((state.reviewMode === 'unified' ? renderUnifiedDiff : renderSplitDiff)(state.diff.oldStr || '', state.diff.newStr || '', mapExtToLanguage(ext)));
26
+ }
@@ -0,0 +1,54 @@
1
+ import { filePreviewKind } from './file-pane-render.js';
2
+ import { iconHtml, refreshIcons } from './icons.js';
3
+
4
+ function button(label, icon, action) {
5
+ var element = document.createElement('button'); element.type = 'button'; element.title = label; element.setAttribute('aria-label', label);
6
+ element.innerHTML = icon ? iconHtml(icon) : label; element.addEventListener('click', action); return element;
7
+ }
8
+ export function filePaneShell(id, act) {
9
+ var panel = document.createElement('section'); panel.id = 'file-' + id; panel.className = 'file-instance'; panel.dataset.fileInstance = id;
10
+ var header = document.createElement('header'); header.className = 'tool-pane-header split-pane-header'; header.dataset.toolPane = panel.id;
11
+ var title = document.createElement('span'); title.className = 'tool-pane-title split-pane-title';
12
+ var space = document.createElement('span'); space.className = 'split-pane-drag-space';
13
+ var actions = document.createElement('div'); actions.className = 'file-viewer-window-actions';
14
+ var pin = button('Pin this file', 'pin', function () { act('pin'); }); pin.dataset.action = 'pin';
15
+ actions.append(pin, button('Expand file pane', 'maximize-2', function () { act('expand'); }), button('Close file pane', 'x', function () { act('close'); }));
16
+ header.append(title, space, actions);
17
+ var toolbar = document.createElement('div'); toolbar.className = 'file-instance-toolbar'; toolbar.setAttribute('aria-label', 'File actions');
18
+ ['Preview', 'Source', 'History'].forEach(function (label) { var b = button(label, null, function () { act(label.toLowerCase()); }); b.dataset.action = label.toLowerCase(); toolbar.appendChild(b); });
19
+ var more = button('More file actions', 'ellipsis', toggle); more.setAttribute('aria-haspopup', 'menu'); more.setAttribute('aria-expanded', 'false');
20
+ toolbar.appendChild(more);
21
+ var menu = document.createElement('div'); menu.className = 'file-instance-more hidden'; menu.setAttribute('role', 'menu');
22
+ [['Copy source', 'copy'], ['Copy formatted', 'formatted'], ['Download', 'download'], ['Export PDF', 'pdf'], ['Present slides', 'slides'], ['Use H1 headings', 'h1'], ['Use H2 headings', 'h2'], ['Use H3 headings', 'h3'], ['Refresh', 'refresh']].forEach(function (item) {
23
+ var b = button(item[0], null, function () { dismiss(true); act(item[1]); }); b.setAttribute('role', 'menuitem'); b.dataset.action = item[1]; menu.appendChild(b);
24
+ });
25
+ function dismiss(focus) { menu.classList.add('hidden'); more.setAttribute('aria-expanded', 'false'); if (focus) more.focus(); }
26
+ function toggle() { var open = menu.classList.contains('hidden'); dismiss(false); if (open) { menu.classList.remove('hidden'); more.setAttribute('aria-expanded', 'true'); menu.querySelector('button:not(:disabled)').focus(); } }
27
+ function outside(event) { if (!menu.contains(event.target) && !more.contains(event.target)) dismiss(false); }
28
+ panel.addEventListener('keydown', function (event) {
29
+ if (!menu.classList.contains('hidden')) {
30
+ if (event.key === 'Escape' || event.key === 'Tab') { dismiss(event.key === 'Escape'); event.stopPropagation(); if (event.key === 'Escape') event.preventDefault(); }
31
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Home' || event.key === 'End') {
32
+ var buttons = Array.from(menu.querySelectorAll('button:not(:disabled)')), index = buttons.indexOf(document.activeElement);
33
+ index = event.key === 'Home' ? 0 : event.key === 'End' ? buttons.length - 1 : (index + (event.key === 'ArrowDown' ? 1 : -1) + buttons.length) % buttons.length;
34
+ buttons[index].focus(); event.preventDefault(); event.stopPropagation();
35
+ }
36
+ return;
37
+ }
38
+ if (event.key === 'Escape') { act('escape'); event.stopPropagation(); }
39
+ if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') act(event.key === 'ArrowRight' ? 'next' : 'previous');
40
+ });
41
+ document.addEventListener('pointerdown', outside);
42
+ panel.addEventListener('clay-tool-pane-dispose', function () { document.removeEventListener('pointerdown', outside); }, { once: true });
43
+ var body = document.createElement('div'); body.className = 'file-viewer-body'; body.tabIndex = 0;
44
+ panel.append(header, toolbar, menu, body); refreshIcons(); return panel;
45
+ }
46
+ export function updateFilePaneToolbar(panel, state) {
47
+ var title = panel.querySelector('.tool-pane-title'); title.textContent = state.resource.path.split('/').pop(); title.title = state.resource.path;
48
+ var pin = panel.querySelector('[data-action="pin"]'), pinned = state.resource.mode === 'pin';
49
+ pin.setAttribute('aria-pressed', String(pinned)); pin.title = pinned ? 'Pinned to ' + state.resource.path + '. Switch to Follow selection' : 'Following file selection. Pin this file'; pin.setAttribute('aria-label', pinned ? 'Follow file selection' : 'Pin this file');
50
+ ['preview', 'source', 'history'].forEach(function (key) { panel.querySelector('[data-action="' + key + '"]').setAttribute('aria-pressed', String(key === 'history' ? state.historyVisible : !state.historyVisible && (key === 'preview') === state.resource.rendered)); });
51
+ panel.querySelector('[data-action="preview"]').disabled = !filePreviewKind(state.resource.path) || !!state.binary;
52
+ var markdown = /\.mdx?$/i.test(state.resource.path);
53
+ ['pdf', 'formatted', 'slides', 'h1', 'h2', 'h3'].forEach(function (key) { panel.querySelector('[data-action="' + key + '"]').disabled = !markdown; });
54
+ }
@@ -0,0 +1,70 @@
1
+ import { iconHtml } from './icons.js';
2
+ export function mapExtToLanguage(ext) {
3
+ var map = {
4
+ js: "javascript", ts: "typescript", jsx: "javascript", tsx: "typescript",
5
+ py: "python", rb: "ruby", go: "go", rs: "rust", java: "java",
6
+ css: "css", html: "xml", xml: "xml", json: "json", yaml: "yaml",
7
+ yml: "yaml", md: "markdown", sh: "bash", bash: "bash", zsh: "bash",
8
+ sql: "sql", c: "c", cpp: "cpp", h: "c", hpp: "cpp",
9
+ cs: "csharp", swift: "swift", kt: "kotlin", vue: "xml", svelte: "xml"
10
+ };
11
+ return map[ext] || null;
12
+ }
13
+
14
+ export function renderCodeWithLineNumbers(bodyEl, content, ext) {
15
+ var lang = mapExtToLanguage(ext);
16
+ var lines = content.split("\n");
17
+ var lineCount = lines.length;
18
+
19
+ var viewer = document.createElement("div");
20
+ viewer.className = "file-viewer-code";
21
+
22
+ var gutter = document.createElement("pre");
23
+ gutter.className = "file-viewer-gutter";
24
+ var nums = [];
25
+ for (var i = 1; i <= lineCount; i++) nums.push(i);
26
+ gutter.textContent = nums.join("\n");
27
+
28
+ var codeWrap = document.createElement("pre");
29
+ codeWrap.className = "file-viewer-code-content";
30
+ var codeEl = document.createElement("code");
31
+ if (lang) codeEl.className = "language-" + lang;
32
+ codeEl.textContent = content;
33
+ codeWrap.appendChild(codeEl);
34
+
35
+ viewer.appendChild(gutter);
36
+ viewer.appendChild(codeWrap);
37
+
38
+ bodyEl.innerHTML = "";
39
+ bodyEl.appendChild(viewer);
40
+
41
+ if (typeof hljs !== "undefined" && lang) {
42
+ hljs.highlightElement(codeEl);
43
+ }
44
+ }
45
+
46
+ export function renderLargeTextFile(bodyEl, content, size) {
47
+ var viewer = document.createElement("div");
48
+ viewer.className = "file-viewer-large-text";
49
+
50
+ var notice = document.createElement("div");
51
+ notice.className = "file-viewer-large-notice";
52
+ notice.innerHTML = iconHtml("file-text") +
53
+ "<span>Large file (" + formatSize(size || 0) + ") — showing plain text for performance</span>";
54
+
55
+ var codeWrap = document.createElement("pre");
56
+ var codeEl = document.createElement("code");
57
+ codeEl.textContent = content;
58
+ codeWrap.appendChild(codeEl);
59
+
60
+ viewer.appendChild(notice);
61
+ viewer.appendChild(codeWrap);
62
+ bodyEl.innerHTML = "";
63
+ bodyEl.appendChild(viewer);
64
+ }
65
+
66
+ export function formatSize(bytes) {
67
+ if (bytes < 1024) return bytes + " B";
68
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
69
+ return (bytes / 1048576).toFixed(1) + " MB";
70
+ }