clay-server 3.4.0-beta.22 → 3.4.0-beta.24

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.
@@ -960,7 +960,7 @@
960
960
  width: 50%;
961
961
  max-width: 720px;
962
962
  min-width: 360px;
963
- border-left: 1px solid var(--border);
963
+ border-right: 1px solid var(--border);
964
964
  background: var(--bg);
965
965
  display: flex;
966
966
  flex-direction: column;
@@ -1011,6 +1011,11 @@
1011
1011
  min-height: 44px;
1012
1012
  }
1013
1013
 
1014
+ .file-viewer-tabs { display: flex; min-width: 0; flex: 1; overflow-x: auto; gap: 2px; }
1015
+ .file-viewer-tab { display: inline-flex; align-items: center; gap: 6px; max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: 0; border-radius: 5px; padding: 4px 7px; background: transparent; color: var(--text-secondary); font: inherit; font-size: 12px; cursor: pointer; }
1016
+ .file-viewer-tab.active { background: var(--sidebar-hover); color: var(--text); }
1017
+ .file-viewer-tab-close { font-size: 16px; line-height: 12px; color: var(--text-muted); }
1018
+
1014
1019
  .file-viewer-path {
1015
1020
  flex: 1;
1016
1021
  font-family: "Roboto Mono", monospace;
@@ -23,6 +23,12 @@ body.pane-mode #sticky-notes-container {
23
23
  display: none !important;
24
24
  }
25
25
 
26
+ body.pane-mode #file-viewer { display: none !important; }
27
+
28
+ body.pane-mode #connect-overlay { background: var(--bg); }
29
+ body.pane-mode #connect-overlay #ascii-logo-canvas { display: none; }
30
+ body.pane-mode #connect-overlay-msg { color: var(--text-dimmer); }
31
+
26
32
  body.pane-mode .notif-banner-container {
27
33
  display: none !important;
28
34
  }
@@ -403,7 +403,7 @@
403
403
  <div id="app">
404
404
  <div id="connect-overlay">
405
405
  <canvas id="ascii-logo-canvas"></canvas>
406
- <p id="connect-overlay-msg">Reconnecting to server…</p>
406
+ <p id="connect-overlay-msg">Connecting…</p>
407
407
  </div>
408
408
  <div id="sticky-notes-container" class="hidden"></div>
409
409
  <div id="debate-info-float" class="hidden"></div>
@@ -680,6 +680,7 @@
680
680
 
681
681
  <div id="file-viewer" class="hidden">
682
682
  <div class="file-viewer-header">
683
+ <div class="file-viewer-tabs" id="file-viewer-tabs" role="tablist"></div>
683
684
  <span class="file-viewer-path" id="file-viewer-path"></span>
684
685
  <span class="file-viewer-live-status hidden" id="file-viewer-live-status" aria-live="polite">
685
686
  <span class="file-viewer-live-dot"></span>
@@ -17,6 +17,7 @@ var reconnectTimer = null;
17
17
  var reconnectDelay = 1000;
18
18
  var connectTimeoutId = null;
19
19
  var connectOverlay = null;
20
+ var hasConnectedOnce = false;
20
21
 
21
22
  export function initConnection() {
22
23
  connectOverlay = document.getElementById("connect-overlay");
@@ -39,6 +40,7 @@ export function initConnection() {
39
40
  if (state.connected !== prev.connected) {
40
41
  var sendBtn = getSendBtn();
41
42
  if (state.connected) {
43
+ hasConnectedOnce = true;
42
44
  if (sendBtn) sendBtn.disabled = false;
43
45
  if (connectOverlay) connectOverlay.classList.add("hidden");
44
46
  var updPill = document.getElementById("update-pill-wrap");
@@ -46,6 +48,10 @@ export function initConnection() {
46
48
  stopLogoAnimation();
47
49
  } else {
48
50
  if (sendBtn) sendBtn.disabled = true;
51
+ if (hasConnectedOnce && connectOverlay) {
52
+ var overlayMessage = document.getElementById("connect-overlay-msg");
53
+ if (overlayMessage) overlayMessage.textContent = "Reconnecting to server…";
54
+ }
49
55
  if (connectOverlay) connectOverlay.classList.remove("hidden");
50
56
  startLogoAnimation();
51
57
  }
@@ -28,6 +28,7 @@ import { startThinking, appendThinking, stopThinking, resetThinkingGroup, create
28
28
  import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
29
29
  import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch, presentMarkdownEdit } from './filebrowser.js';
30
30
  import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
31
+ import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
31
32
  import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
32
33
  import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
33
34
  import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
@@ -1400,7 +1401,10 @@ export function processMessage(msg) {
1400
1401
  break;
1401
1402
 
1402
1403
  case "markdown_edit_present":
1403
- if (!store.get('replayingHistory') && !store.get('dmMode')) presentMarkdownEdit(msg);
1404
+ if (!store.get('replayingHistory') && !store.get('dmMode')) {
1405
+ if (store.get('paneMode')) forwardPaneMarkdownPresentation(msg);
1406
+ else if (!store.get('splitPanes')) presentMarkdownEdit(msg);
1407
+ }
1404
1408
  break;
1405
1409
 
1406
1410
  case "fs_dir_changed":
@@ -0,0 +1,65 @@
1
+ var tabs = new Map();
2
+ var focusedPath = null;
3
+ var onFocus = null;
4
+ var onEmpty = null;
5
+
6
+ function label(path) {
7
+ var parts = String(path || "").split("/");
8
+ return parts[parts.length - 1] || path;
9
+ }
10
+
11
+ function render() {
12
+ var root = document.getElementById("file-viewer-tabs");
13
+ if (!root) return;
14
+ root.innerHTML = "";
15
+ tabs.forEach(function(value, path) {
16
+ var tab = document.createElement("button");
17
+ tab.type = "button";
18
+ tab.className = "file-viewer-tab" + (path === focusedPath ? " active" : "");
19
+ tab.textContent = label(path);
20
+ tab.title = path;
21
+ tab.addEventListener("click", function() { focusFileViewerTab(path); });
22
+ var close = document.createElement("span");
23
+ close.className = "file-viewer-tab-close";
24
+ close.textContent = "×";
25
+ close.addEventListener("click", function(event) { event.stopPropagation(); closeFileViewerTab(path); });
26
+ tab.appendChild(close);
27
+ root.appendChild(tab);
28
+ });
29
+ }
30
+
31
+ export function initFileViewerTabs(options) {
32
+ onFocus = options && options.onFocus;
33
+ onEmpty = options && options.onEmpty;
34
+ }
35
+
36
+ export function openFileViewerTab(path, data) {
37
+ if (!path) return false;
38
+ var exists = tabs.has(path);
39
+ tabs.set(path, Object.assign(tabs.get(path) || {}, data || {}));
40
+ focusedPath = path;
41
+ render();
42
+ return !exists;
43
+ }
44
+
45
+ export function focusFileViewerTab(path) {
46
+ if (!tabs.has(path)) return false;
47
+ focusedPath = path;
48
+ render();
49
+ if (onFocus) onFocus(path, tabs.get(path));
50
+ return true;
51
+ }
52
+
53
+ export function closeFileViewerTab(path) {
54
+ if (!tabs.has(path)) return;
55
+ var paths = Array.from(tabs.keys());
56
+ var index = paths.indexOf(path);
57
+ tabs.delete(path);
58
+ if (focusedPath === path) focusedPath = paths[index + 1] || paths[index - 1] || null;
59
+ render();
60
+ if (focusedPath && onFocus) onFocus(focusedPath, tabs.get(focusedPath));
61
+ if (!focusedPath && onEmpty) onEmpty();
62
+ }
63
+
64
+ export function focusedFileViewerTab() { return focusedPath; }
65
+ export function clearFileViewerTabs() { tabs.clear(); focusedPath = null; render(); }
@@ -9,6 +9,7 @@ import { copyMarkdownFormatting } from './rich-clipboard.js';
9
9
  import { animateMarkdownChange, beginMarkdownPresentation, cancelMarkdownFollow, isFollowingMarkdown } from './markdown-live-edit.js';
10
10
  import { store } from './store.js';
11
11
  import { enterMarkdownSlides, exitMarkdownSlides, handleMarkdownSlideKey, syncMarkdownSlidesButton, toggleMarkdownSlideLevelMenu } from './markdown-slides.js';
12
+ import { initFileViewerTabs, openFileViewerTab, closeFileViewerTab, focusedFileViewerTab, clearFileViewerTabs } from './filebrowser-tabs.js';
12
13
 
13
14
  var ctx;
14
15
  var showDropHint = function () {};
@@ -30,6 +31,14 @@ var pendingFileAt = null; // callback for pending file-at
30
31
 
31
32
  export function initFileBrowser(_ctx) {
32
33
  ctx = _ctx;
34
+ var mainPanels = document.getElementById("main-panels");
35
+ if (mainPanels && ctx.fileViewerEl) mainPanels.insertBefore(ctx.fileViewerEl, mainPanels.firstChild);
36
+ initFileViewerTabs({
37
+ onFocus: function(path) {
38
+ if (path !== currentFilePath) requestFileContent(path);
39
+ },
40
+ onEmpty: function() { teardownFileViewer(); },
41
+ });
33
42
 
34
43
  // Load material file icons in background
35
44
  initFileIcons().then(function () {
@@ -133,7 +142,7 @@ export function initFileBrowser(_ctx) {
133
142
 
134
143
  // Close button
135
144
  document.getElementById("file-viewer-close").addEventListener("click", function () {
136
- closeFileViewer();
145
+ closeFileViewerTab(focusedFileViewerTab());
137
146
  });
138
147
 
139
148
  // Full-viewport presentation toggle
@@ -448,6 +457,14 @@ function ensureRenderedMarkdown() {
448
457
  }
449
458
 
450
459
  export function closeFileViewer() {
460
+ if (focusedFileViewerTab()) {
461
+ closeFileViewerTab(focusedFileViewerTab());
462
+ return;
463
+ }
464
+ teardownFileViewer();
465
+ }
466
+
467
+ function teardownFileViewer() {
451
468
  if (currentFilePath && isFollowingMarkdown(currentFilePath)) cancelMarkdownFollow();
452
469
  sendUnwatch();
453
470
  inlineDiffActive = false;
@@ -475,8 +492,8 @@ export function setFileViewerFullscreen(enabled) {
475
492
  }
476
493
 
477
494
  export function resetFileBrowser() {
478
- // Close viewer
479
- closeFileViewer();
495
+ clearFileViewerTabs();
496
+ teardownFileViewer();
480
497
  // Clear all cached state
481
498
  treeData = {};
482
499
  currentContent = null;
@@ -513,6 +530,7 @@ export function openFile(filePath, opts) {
513
530
  cancelMarkdownFollow();
514
531
  }
515
532
  pendingRenderedOpen = !!(opts && opts.rendered);
533
+ openFileViewerTab(filePath);
516
534
  if (opts && opts.diff) {
517
535
  pendingOpenMode = { type: "diff", oldStr: opts.diff.oldStr, newStr: opts.diff.newStr };
518
536
  } else {
@@ -540,6 +558,7 @@ export function openWorkingTreeDiff(diff) {
540
558
 
541
559
  export function presentMarkdownEdit(msg) {
542
560
  if (!msg || !beginMarkdownPresentation(msg.path)) return;
561
+ openFileViewerTab(msg.path, { content: msg.content });
543
562
  pendingOpenMode = null;
544
563
  pendingRenderedOpen = true;
545
564
  pendingRefresh = false;
@@ -1031,6 +1050,7 @@ function renderEntries(container, entries, depth) {
1031
1050
  // --- File viewer ---
1032
1051
 
1033
1052
  function showFileContent(msg) {
1053
+ openFileViewerTab(msg.path, { content: msg.content });
1034
1054
  var pathEl = document.getElementById("file-viewer-path");
1035
1055
  var bodyEl = document.getElementById("file-viewer-body");
1036
1056
  var renderBtn = document.getElementById("file-viewer-render");
@@ -23,6 +23,12 @@ export function reportPaneContext(data) {
23
23
  }, window.location.origin);
24
24
  }
25
25
 
26
+ export function forwardPaneMarkdownPresentation(message) {
27
+ if (!store.get('paneMode') || window.parent === window) return false;
28
+ window.parent.postMessage({ type: "clay-pane-present-markdown", message: message }, window.location.origin);
29
+ return true;
30
+ }
31
+
26
32
  // Toggle without setContextView: panes share localStorage with the main app,
27
33
  // so persisting here would silently rewrite the user's main-view preference.
28
34
  function togglePaneContextPanel() {
@@ -10,6 +10,7 @@ import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
10
10
  import { groupedSessionIds, findSplitGroup } from './split-group-helpers.js';
11
11
  import { showConfirm } from './app-misc.js';
12
12
  import { syncPairChrome } from './split-pair-ui.js';
13
+ import { presentMarkdownEdit } from './filebrowser.js';
13
14
 
14
15
  var host = null;
15
16
  var nativeApp = null;
@@ -269,7 +270,13 @@ function updatePaneContextChip(paneEl, msg) {
269
270
  function handlePaneMessage(event) {
270
271
  if (event.origin !== window.location.origin) return;
271
272
  var msg = event.data;
272
- if (!msg || msg.type !== "clay-pane-context" || !host) return;
273
+ if (!msg || !host) return;
274
+ if (msg.type === "clay-pane-present-markdown") {
275
+ var present = msg.message;
276
+ if (present) presentMarkdownEdit(present);
277
+ return;
278
+ }
279
+ if (msg.type !== "clay-pane-context") return;
273
280
  var frames = host.querySelectorAll(".split-pane-frame");
274
281
  for (var i = 0; i < frames.length; i++) {
275
282
  if (frames[i].contentWindow === event.source) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.4.0-beta.22",
3
+ "version": "3.4.0-beta.24",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",