clay-server 3.5.1 → 3.6.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +54 -196
  2. package/bin/cli.js +7 -42
  3. package/lib/builtin-mates.js +1 -1
  4. package/lib/cli-wordmark.js +32 -0
  5. package/lib/daemon.js +7 -0
  6. package/lib/os-user-diagnostics-worker.js +18 -0
  7. package/lib/os-user-diagnostics.js +312 -0
  8. package/lib/os-users.js +55 -22
  9. package/lib/pages.js +47 -35
  10. package/lib/public/app.js +2 -10
  11. package/lib/public/apple-touch-icon.png +0 -0
  12. package/lib/public/clay-studio-favicon-32.png +0 -0
  13. package/lib/public/clay-studio-symbol.png +0 -0
  14. package/lib/public/clay-studio-wordmark.svg +20 -0
  15. package/lib/public/css/base.css +143 -61
  16. package/lib/public/css/filebrowser.css +132 -22
  17. package/lib/public/css/icon-strip.css +33 -19
  18. package/lib/public/css/input.css +15 -5
  19. package/lib/public/css/messages.css +17 -14
  20. package/lib/public/css/mobile-nav.css +5 -5
  21. package/lib/public/css/overlays.css +58 -6
  22. package/lib/public/css/pane.css +0 -1
  23. package/lib/public/css/session-actions.css +9 -3
  24. package/lib/public/css/sidebar.css +104 -161
  25. package/lib/public/css/title-bar.css +51 -18
  26. package/lib/public/css/user-settings.css +114 -8
  27. package/lib/public/icon-192.png +0 -0
  28. package/lib/public/icon-512.png +0 -0
  29. package/lib/public/index.html +44 -82
  30. package/lib/public/manifest.json +1 -6
  31. package/lib/public/modules/app-connection.js +0 -3
  32. package/lib/public/modules/app-favicon.js +22 -29
  33. package/lib/public/modules/app-messages.js +1 -2
  34. package/lib/public/modules/app-notifications.js +3 -3
  35. package/lib/public/modules/command-palette.js +1 -1
  36. package/lib/public/modules/filebrowser-tabs.js +42 -3
  37. package/lib/public/modules/filebrowser.js +78 -9
  38. package/lib/public/modules/server-settings.js +0 -77
  39. package/lib/public/modules/sidebar-projects.js +1 -1
  40. package/lib/public/modules/sidebar-sessions.js +18 -5
  41. package/lib/public/modules/sidebar.js +2 -2
  42. package/lib/public/modules/theme.js +88 -249
  43. package/lib/public/modules/user-settings.js +2 -3
  44. package/lib/public/style.css +10 -10
  45. package/lib/public/sw.js +4 -2
  46. package/lib/themes/clay-dark.json +11 -0
  47. package/lib/themes/clay-light.json +11 -0
  48. package/package.json +1 -1
  49. package/lib/public/apple-touch-icon-dark.png +0 -0
  50. package/lib/public/clay-logo.png +0 -0
  51. package/lib/public/favicon-banded-32.png +0 -0
  52. package/lib/public/favicon-banded.png +0 -0
  53. package/lib/public/favicon-dark.svg +0 -1
  54. package/lib/public/favicon.svg +0 -1
  55. package/lib/public/icon-192-dark.png +0 -0
  56. package/lib/public/icon-512-dark.png +0 -0
  57. package/lib/public/icon-banded-76.png +0 -0
  58. package/lib/public/icon-banded-96.png +0 -0
  59. package/lib/public/icon-mono.svg +0 -1
  60. package/lib/public/modules/ascii-logo.js +0 -442
  61. package/lib/public/wordmark-banded-20.png +0 -0
  62. package/lib/public/wordmark-banded-32.png +0 -0
  63. package/lib/public/wordmark-banded-64.png +0 -0
  64. package/lib/public/wordmark-banded-80.png +0 -0
@@ -30,7 +30,7 @@ import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handle
30
30
  import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
31
31
  import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
32
32
  import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
33
- import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
33
+ import { updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
34
34
  import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
35
35
  import { attachTuiView, detachTuiView, setTuiSuspendedView, tuiHandleTermOutput, tuiHandleTermResized, tuiHandleTermExited, tuiHandleTermClosed } from './session-tui-view.js';
36
36
  import { handleTuiTranscriptState } from './tui-grab.js';
@@ -467,7 +467,6 @@ export function processMessage(msg) {
467
467
  if (msg.vendor && !store.get('vendorSelectionLocked')) _miUpdate.currentVendor = msg.vendor;
468
468
  if (msg.capabilities) _miUpdate.vendorCapabilities = msg.capabilities;
469
469
  store.set(_miUpdate);
470
- updateSettingsModels(_modelVal, msg.models || []);
471
470
  break;
472
471
  }
473
472
 
@@ -138,11 +138,11 @@ function showBanner(notif, autoDismissMs) {
138
138
  : mate
139
139
  ? '<img class="notif-banner-avatar" src="' + escapeHtml(mateAvatarUrl(mate, 32)) + '" alt="' + escapeHtml(mate.displayName || mate.name || "Mate") + '">'
140
140
  : isAuthRequired
141
- ? '<img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px">'
141
+ ? '<img src="/clay-studio-symbol.png" width="32" height="32" alt="Clay Studio">'
142
142
  : projectIcon
143
143
  ? '<span class="notif-banner-emoji">' + projectIcon + '</span>'
144
144
  : isEmpty
145
- ? '<img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px">'
145
+ ? '<img src="/clay-studio-symbol.png" width="32" height="32" alt="Clay Studio">'
146
146
  : iconHtml("folder");
147
147
 
148
148
  // Format permission title as "Can I ..." style
@@ -828,7 +828,7 @@ export function showUpdateBanner(msg) {
828
828
  }
829
829
 
830
830
  banner.innerHTML =
831
- '<div class="notif-banner-icon"><img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px"></div>' +
831
+ '<div class="notif-banner-icon"><img src="/clay-studio-symbol.png" width="32" height="32" alt="Clay Studio"></div>' +
832
832
  '<div class="notif-banner-body">' +
833
833
  '<div class="notif-banner-project">CLAY</div>' +
834
834
  '<div class="notif-banner-title">v' + escapeHtml(msg.version) + ' is available</div>' +
@@ -195,7 +195,7 @@ function updateFooter() {
195
195
  '<span><kbd>Enter</kbd> select</span>';
196
196
  var versionText = cachedVersion ? "v" + cachedVersion : "";
197
197
  footerEl.innerHTML =
198
- '<a href="https://github.com/chadbyte/clay" target="_blank" rel="noopener" class="cmd-palette-brand"><img src="favicon-banded.png" width="13" height="13" alt="">Clay ' + versionText + '</a>' +
198
+ '<a href="https://github.com/chadbyte/clay" target="_blank" rel="noopener" class="cmd-palette-brand"><img src="clay-studio-symbol.png" width="13" height="13" alt="">Clay Studio ' + versionText + '</a>' +
199
199
  '<span class="cmd-palette-footer-shortcuts">' + shortcuts + '</span>';
200
200
  }
201
201
 
@@ -1,5 +1,8 @@
1
+ import { getFileIconSvg } from './fileicons.js';
2
+
1
3
  var tabs = new Map();
2
4
  var focusedPath = null;
5
+ var previewPath = null;
3
6
  var onFocus = null;
4
7
  var onEmpty = null;
5
8
 
@@ -15,14 +18,23 @@ function render() {
15
18
  tabs.forEach(function(value, path) {
16
19
  var tab = document.createElement("button");
17
20
  tab.type = "button";
18
- tab.className = "file-viewer-tab" + (path === focusedPath ? " active" : "");
19
- tab.textContent = label(path);
21
+ tab.className = "file-viewer-tab" + (path === focusedPath ? " active" : "") + (path === previewPath ? " preview" : "");
22
+ tab.setAttribute("role", "tab");
23
+ tab.setAttribute("aria-selected", path === focusedPath ? "true" : "false");
20
24
  tab.title = path;
21
25
  tab.addEventListener("click", function() { focusFileViewerTab(path); });
26
+ var icon = document.createElement("span");
27
+ icon.className = "file-viewer-tab-icon";
28
+ icon.innerHTML = getFileIconSvg(label(path));
29
+ var name = document.createElement("span");
30
+ name.className = "file-viewer-tab-label";
31
+ name.textContent = label(path);
22
32
  var close = document.createElement("span");
23
33
  close.className = "file-viewer-tab-close";
24
34
  close.textContent = "×";
25
35
  close.addEventListener("click", function(event) { event.stopPropagation(); closeFileViewerTab(path); });
36
+ tab.appendChild(icon);
37
+ tab.appendChild(name);
26
38
  tab.appendChild(close);
27
39
  root.appendChild(tab);
28
40
  });
@@ -37,11 +49,37 @@ export function openFileViewerTab(path, data) {
37
49
  if (!path) return false;
38
50
  var exists = tabs.has(path);
39
51
  tabs.set(path, Object.assign(tabs.get(path) || {}, data || {}));
52
+ if (path === previewPath) previewPath = null;
53
+ focusedPath = path;
54
+ render();
55
+ return !exists;
56
+ }
57
+
58
+ export function previewFileViewerTab(path, data) {
59
+ if (!path) return false;
60
+ if (tabs.has(path) && path !== previewPath) {
61
+ focusedPath = path;
62
+ render();
63
+ return false;
64
+ }
65
+ if (previewPath && previewPath !== path) tabs.delete(previewPath);
66
+ var exists = tabs.has(path);
67
+ tabs.set(path, Object.assign(tabs.get(path) || {}, data || {}));
68
+ previewPath = path;
40
69
  focusedPath = path;
41
70
  render();
42
71
  return !exists;
43
72
  }
44
73
 
74
+ export function updateFileViewerTab(path, data) {
75
+ if (!path) return false;
76
+ if (!tabs.has(path)) return false;
77
+ tabs.set(path, Object.assign(tabs.get(path) || {}, data || {}));
78
+ if (focusedPath !== path) return false;
79
+ render();
80
+ return true;
81
+ }
82
+
45
83
  export function focusFileViewerTab(path) {
46
84
  if (!tabs.has(path)) return false;
47
85
  focusedPath = path;
@@ -55,6 +93,7 @@ export function closeFileViewerTab(path) {
55
93
  var paths = Array.from(tabs.keys());
56
94
  var index = paths.indexOf(path);
57
95
  tabs.delete(path);
96
+ if (previewPath === path) previewPath = null;
58
97
  if (focusedPath === path) focusedPath = paths[index + 1] || paths[index - 1] || null;
59
98
  render();
60
99
  if (focusedPath && onFocus) onFocus(focusedPath, tabs.get(focusedPath));
@@ -62,4 +101,4 @@ export function closeFileViewerTab(path) {
62
101
  }
63
102
 
64
103
  export function focusedFileViewerTab() { return focusedPath; }
65
- export function clearFileViewerTabs() { tabs.clear(); focusedPath = null; render(); }
104
+ export function clearFileViewerTabs() { tabs.clear(); focusedPath = null; previewPath = null; render(); }
@@ -9,7 +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
+ import { initFileViewerTabs, openFileViewerTab, previewFileViewerTab, updateFileViewerTab, closeFileViewerTab, focusedFileViewerTab, clearFileViewerTabs } from './filebrowser-tabs.js';
13
13
 
14
14
  var ctx;
15
15
  var showDropHint = function () {};
@@ -18,6 +18,7 @@ var currentContent = null; // last read file content for copy
18
18
  var currentFilePath = null; // path of the currently viewed file
19
19
  var isRendered = false; // markdown render toggle state
20
20
  var currentIsMarkdown = false;
21
+ var currentIsSvg = false;
21
22
  var historyVisible = false;
22
23
  var currentHistoryEntries = [];
23
24
  var pendingNavigate = null; // { sessionLocalId, assistantUuid }
@@ -181,8 +182,12 @@ export function initFileBrowser(_ctx) {
181
182
 
182
183
  // Markdown render toggle
183
184
  document.getElementById("file-viewer-render").addEventListener("click", function () {
184
- if (!currentContent || !currentIsMarkdown) return;
185
+ if (!currentContent || (!currentIsMarkdown && !currentIsSvg)) return;
185
186
  isRendered = !isRendered;
187
+ if (currentIsSvg) {
188
+ renderSvgBody();
189
+ return;
190
+ }
186
191
  if (!isRendered && isFollowingMarkdown(currentFilePath)) cancelMarkdownFollow();
187
192
  renderBody();
188
193
  });
@@ -377,7 +382,11 @@ function kbCollapseOrAscend() {
377
382
  }
378
383
 
379
384
  function kbActivate() {
380
- if (_kbFocused) _kbFocused.click();
385
+ if (!_kbFocused) return;
386
+ if (!isDirRow(_kbFocused) && _kbFocused.dataset.path) {
387
+ openFileViewerTab(_kbFocused.dataset.path);
388
+ }
389
+ _kbFocused.click();
381
390
  }
382
391
 
383
392
  function handleTreeKeyDown(e) {
@@ -500,6 +509,7 @@ export function resetFileBrowser() {
500
509
  currentFilePath = null;
501
510
  isRendered = false;
502
511
  currentIsMarkdown = false;
512
+ currentIsSvg = false;
503
513
  historyVisible = false;
504
514
  currentHistoryEntries = [];
505
515
  pendingNavigate = null;
@@ -541,6 +551,7 @@ export function openFile(filePath, opts) {
541
551
 
542
552
  export function openWorkingTreeDiff(diff) {
543
553
  if (!diff || !diff.path) return;
554
+ openFileViewerTab(diff.path);
544
555
  pendingRenderedOpen = false;
545
556
  pendingOpenMode = diff.binary ? null : {
546
557
  type: "diff",
@@ -801,9 +812,14 @@ function renderFilteredTree(container, tree, depth, query) {
801
812
  var prev = ctx.fileTreeEl.querySelector(".file-tree-item.active");
802
813
  if (prev) prev.classList.remove("active");
803
814
  rowEl.classList.add("active");
815
+ previewFileViewerTab(filePath);
804
816
  requestFileContent(filePath);
805
817
  if (window.innerWidth <= 768) closeSidebar();
806
818
  });
819
+ rowEl.addEventListener("dblclick", function (e) {
820
+ e.stopPropagation();
821
+ openFileViewerTab(filePath);
822
+ });
807
823
  })(entry.path, row);
808
824
 
809
825
  container.appendChild(row);
@@ -1033,12 +1049,17 @@ function renderEntries(container, entries, depth) {
1033
1049
  var prev = ctx.fileTreeEl.querySelector(".file-tree-item.active");
1034
1050
  if (prev) prev.classList.remove("active");
1035
1051
  rowEl.classList.add("active");
1052
+ previewFileViewerTab(filePath);
1036
1053
  requestFileContent(filePath);
1037
1054
  // Mobile: close sidebar
1038
1055
  if (window.innerWidth <= 768) {
1039
1056
  closeSidebar();
1040
1057
  }
1041
1058
  });
1059
+ rowEl.addEventListener("dblclick", function (e) {
1060
+ e.stopPropagation();
1061
+ openFileViewerTab(filePath);
1062
+ });
1042
1063
  })(entry.path, row);
1043
1064
 
1044
1065
  container.appendChild(row);
@@ -1050,7 +1071,7 @@ function renderEntries(container, entries, depth) {
1050
1071
  // --- File viewer ---
1051
1072
 
1052
1073
  function showFileContent(msg) {
1053
- openFileViewerTab(msg.path, { content: msg.content });
1074
+ if (!updateFileViewerTab(msg.path, { content: msg.content })) return;
1054
1075
  var pathEl = document.getElementById("file-viewer-path");
1055
1076
  var bodyEl = document.getElementById("file-viewer-body");
1056
1077
  var renderBtn = document.getElementById("file-viewer-render");
@@ -1064,13 +1085,14 @@ function showFileContent(msg) {
1064
1085
 
1065
1086
  exitMarkdownSlides();
1066
1087
 
1067
- pathEl.textContent = msg.path;
1088
+ renderFileBreadcrumb(pathEl, msg.path);
1068
1089
  var keepRenderState = pendingRefresh && msg.path === currentFilePath;
1069
1090
  var prevRendered = isRendered;
1070
1091
  pendingRefresh = false;
1071
1092
  currentContent = null;
1072
1093
  currentFilePath = msg.path;
1073
1094
  currentIsMarkdown = false;
1095
+ currentIsSvg = false;
1074
1096
  if (!keepRenderState) isRendered = false;
1075
1097
  var requestedExt = msg.path.split(".").pop().toLowerCase();
1076
1098
  if (pendingRenderedOpen && (requestedExt === "md" || requestedExt === "mdx")) {
@@ -1095,23 +1117,27 @@ function showFileContent(msg) {
1095
1117
  currentContent = msg.content;
1096
1118
  var ext = requestedExt;
1097
1119
  currentIsMarkdown = (ext === "md" || ext === "mdx");
1120
+ currentIsSvg = ext === "svg";
1098
1121
  if (pendingRenderedOpen && currentIsMarkdown) isRendered = true;
1099
1122
 
1100
- if (currentIsMarkdown) {
1123
+ if (currentIsMarkdown || currentIsSvg) {
1101
1124
  renderBtn.classList.remove("hidden");
1102
- renderBtn.title = "Render markdown";
1103
- copyBtn.title = "Copy Markdown source";
1125
+ renderBtn.title = currentIsSvg ? "Show SVG source" : "Render markdown";
1126
+ copyBtn.title = currentIsSvg ? "Copy SVG source" : "Copy Markdown source";
1104
1127
  } else {
1105
1128
  copyBtn.title = "Copy contents";
1106
1129
  }
1107
1130
 
1108
- // Show raw by default, use renderBody for markdown toggle
1131
+ // Markdown starts as source; SVG starts as a safe image preview.
1109
1132
  if (currentIsMarkdown) {
1110
1133
  var transitionFrom = keepRenderState && prevRendered && previousWasMarkdown &&
1111
1134
  isFollowingMarkdown(msg.path) && pathsReferToSameFile(previousPath, msg.path)
1112
1135
  ? (previousContent == null ? "" : previousContent)
1113
1136
  : null;
1114
1137
  renderBody(transitionFrom, refreshSlideIndex);
1138
+ } else if (currentIsSvg) {
1139
+ if (!keepRenderState) isRendered = true;
1140
+ renderSvgBody();
1115
1141
  } else {
1116
1142
  renderCodeWithLineNumbers(bodyEl, msg.content, ext);
1117
1143
  }
@@ -1158,6 +1184,47 @@ function showFileContent(msg) {
1158
1184
  }
1159
1185
  }
1160
1186
 
1187
+ function renderSvgBody() {
1188
+ var bodyEl = document.getElementById("file-viewer-body");
1189
+ var renderBtn = document.getElementById("file-viewer-render");
1190
+ if (!bodyEl || !currentFilePath || currentContent == null) return;
1191
+ if (!isRendered) {
1192
+ renderCodeWithLineNumbers(bodyEl, currentContent, "svg");
1193
+ renderBtn.classList.remove("active");
1194
+ renderBtn.title = "Show SVG preview";
1195
+ return;
1196
+ }
1197
+ bodyEl.innerHTML = "";
1198
+ var preview = document.createElement("div");
1199
+ preview.className = "file-viewer-svg-preview";
1200
+ var image = document.createElement("img");
1201
+ image.src = "api/file?path=" + encodeURIComponent(currentFilePath);
1202
+ image.alt = currentFilePath;
1203
+ image.draggable = false;
1204
+ preview.appendChild(image);
1205
+ bodyEl.appendChild(preview);
1206
+ renderBtn.classList.add("active");
1207
+ renderBtn.title = "Show SVG source";
1208
+ }
1209
+
1210
+ function renderFileBreadcrumb(root, path) {
1211
+ root.innerHTML = "";
1212
+ root.title = path;
1213
+ var parts = String(path || "").replace(/\\/g, "/").split("/").filter(function(part) { return part; });
1214
+ for (var i = 0; i < parts.length; i++) {
1215
+ if (i > 0) {
1216
+ var separator = document.createElement("span");
1217
+ separator.className = "file-viewer-path-separator";
1218
+ separator.textContent = "›";
1219
+ root.appendChild(separator);
1220
+ }
1221
+ var segment = document.createElement("span");
1222
+ segment.className = "file-viewer-path-segment" + (i === parts.length - 1 ? " current" : "");
1223
+ segment.textContent = parts[i];
1224
+ root.appendChild(segment);
1225
+ }
1226
+ }
1227
+
1161
1228
  function pathsReferToSameFile(left, right) {
1162
1229
  if (!left || !right) return false;
1163
1230
  var a = String(left).replace(/\\/g, "/");
@@ -1454,6 +1521,8 @@ function rerenderFileContent() {
1454
1521
 
1455
1522
  if (currentIsMarkdown) {
1456
1523
  renderBody();
1524
+ } else if (currentIsSvg) {
1525
+ renderSvgBody();
1457
1526
  } else {
1458
1527
  renderCodeWithLineNumbers(bodyEl, currentContent, ext);
1459
1528
  }
@@ -4,7 +4,6 @@ import { showToast, copyToClipboard } from './utils.js';
4
4
  import { parseEnvString, looksLikeEnv } from './project-settings.js';
5
5
  import { checkAdminAccess, loadAdminSection } from './admin.js';
6
6
  import { closeFileViewer } from './filebrowser.js';
7
- import { renderModelList, renderModeList, renderEffortBar, renderThinkingBar, renderBetaCard, isSonnetModel } from './settings-defaults.js';
8
7
 
9
8
  var ctx = null;
10
9
  var settingsEl = null;
@@ -111,18 +110,6 @@ export function initServerSettings(appCtx) {
111
110
  });
112
111
  }
113
112
 
114
- // Model item click
115
- settingsEl.addEventListener("click", function (e) {
116
- var modelItem = e.target.closest(".settings-model-item");
117
- if (!modelItem) return;
118
- var model = modelItem.dataset.model;
119
- if (!model) return;
120
- var ws = ctx.ws;
121
- if (ws && ws.readyState === 1) {
122
- ws.send(JSON.stringify({ type: "set_server_default_model", model: model }));
123
- }
124
- });
125
-
126
113
  // PIN buttons
127
114
  var pinSetBtn = document.getElementById("settings-pin-set-btn");
128
115
  var pinRemoveBtn = document.getElementById("settings-pin-remove-btn");
@@ -348,12 +335,6 @@ function populateSettings() {
348
335
  // Sync notification toggles
349
336
  syncNotifToggles();
350
337
 
351
- // Session defaults
352
- updateModelList();
353
- updateModeList();
354
- updateEffortBar();
355
- updateThinkingBar();
356
- updateSsBetaCard();
357
338
  }
358
339
 
359
340
  function syncNotifToggles() {
@@ -369,51 +350,6 @@ function syncNotifToggles() {
369
350
  }
370
351
  }
371
352
 
372
- function ssSendMsg(type, data) {
373
- var ws = ctx.ws;
374
- if (ws && ws.readyState === 1) {
375
- var msg = Object.assign({ type: type }, data);
376
- ws.send(JSON.stringify(msg));
377
- }
378
- }
379
-
380
- function ssDefaultsOpts() {
381
- return {
382
- models: ctx.currentModels || [],
383
- currentModel: ctx.currentModel || ctx._currentModelValue || "",
384
- currentMode: ctx.currentMode || "default",
385
- currentEffort: ctx.currentEffort || "medium",
386
- currentThinking: ctx.currentThinking || "adaptive",
387
- currentThinkingBudget: ctx.currentThinkingBudget || 10000,
388
- currentBetas: ctx.currentBetas || [],
389
- sendMsg: ssSendMsg,
390
- modelMsgType: "set_model",
391
- modeMsgType: "set_server_default_mode",
392
- effortMsgType: "set_server_default_effort",
393
- onModelSelect: function (model) { updateSsBetaCard(model); },
394
- };
395
- }
396
-
397
- function updateModelList() {
398
- renderModelList("ss", ssDefaultsOpts());
399
- }
400
-
401
- function updateModeList() {
402
- renderModeList("ss", ssDefaultsOpts());
403
- }
404
-
405
- function updateEffortBar() {
406
- renderEffortBar("ss", ssDefaultsOpts());
407
- }
408
-
409
- function updateThinkingBar() {
410
- renderThinkingBar("ss", ssDefaultsOpts());
411
- }
412
-
413
- function updateSsBetaCard(overrideModel) {
414
- renderBetaCard("ss", Object.assign(ssDefaultsOpts(), { overrideModel: overrideModel }));
415
- }
416
-
417
353
  export function updateSettingsStats(data) {
418
354
  if (!isSettingsOpen()) return;
419
355
  var pid = document.getElementById("settings-status-pid");
@@ -429,19 +365,6 @@ export function updateSettingsStats(data) {
429
365
  if (clients) clients.textContent = String(data.clients);
430
366
  }
431
367
 
432
- export function updateSettingsModels(current, models) {
433
- if (!ctx) return;
434
- ctx.currentModels = models;
435
- ctx._currentModelValue = current;
436
- if (isSettingsOpen()) {
437
- updateModelList();
438
- updateModeList();
439
- updateEffortBar();
440
- updateThinkingBar();
441
- updateSsBetaCard();
442
- }
443
- }
444
-
445
368
  // --- Daemon config ---
446
369
  function requestDaemonConfig() {
447
370
  var ws = ctx.ws;
@@ -275,7 +275,7 @@ export function initSidebarProjects() {
275
275
  // Tooltip + click for home icon
276
276
  var homeIcon = document.querySelector(".icon-strip-home");
277
277
  if (homeIcon) {
278
- homeIcon.addEventListener("mouseenter", function () { showIconTooltip(homeIcon, "Clay"); });
278
+ homeIcon.addEventListener("mouseenter", function () { showIconTooltip(homeIcon, "Clay Studio"); });
279
279
  homeIcon.addEventListener("mouseleave", hideIconTooltip);
280
280
  homeIcon.addEventListener("click", function (e) {
281
281
  e.preventDefault();
@@ -1252,14 +1252,19 @@ function renderSessionItem(s) {
1252
1252
  el.className = "session-item" + (s.active ? " active" : "") + (isMatch ? " search-match" : "");
1253
1253
  el.dataset.sessionId = s.id;
1254
1254
 
1255
+ // Keep agent identity separate from the title so each row has a stable
1256
+ // visual anchor instead of mixing a tiny inline logo into the text.
1257
+ var itemVendor = s.vendor || "claude";
1258
+ var vendorIcon = document.createElement("img");
1259
+ vendorIcon.className = "session-vendor-icon session-vendor-mark";
1260
+ vendorIcon.src = VENDOR_AVATARS[itemVendor] || VENDOR_AVATARS.claude;
1261
+ vendorIcon.alt = "";
1262
+ vendorIcon.title = VENDOR_NAMES[itemVendor] || itemVendor;
1263
+ el.appendChild(vendorIcon);
1264
+
1255
1265
  var textSpan = document.createElement("span");
1256
1266
  textSpan.className = "session-item-text";
1257
1267
  var textHtml = "";
1258
- // Which agent runtime this session ran on. Legacy sessions without a
1259
- // recorded vendor are Claude (pre-multi-vendor era).
1260
- var itemVendor = s.vendor || "claude";
1261
- textHtml += '<img class="session-vendor-icon" src="' + (VENDOR_AVATARS[itemVendor] || VENDOR_AVATARS.claude) +
1262
- '" alt="" title="' + escapeHtml(VENDOR_NAMES[itemVendor] || itemVendor) + '">';
1263
1268
  if (s.isProcessing) {
1264
1269
  textHtml += '<span class="session-processing"></span>';
1265
1270
  }
@@ -1276,6 +1281,14 @@ function renderSessionItem(s) {
1276
1281
  textSpan.innerHTML = textHtml;
1277
1282
  el.appendChild(textSpan);
1278
1283
 
1284
+ var ageText = relativeTime(s.lastActivity || 0);
1285
+ if (ageText) {
1286
+ var age = document.createElement("span");
1287
+ age.className = "session-item-age";
1288
+ age.textContent = ageText;
1289
+ el.appendChild(age);
1290
+ }
1291
+
1279
1292
  // Right-click / long-press: context menu
1280
1293
  el.addEventListener("contextmenu", (function(id, title, cliSid, anchor, sData) {
1281
1294
  return function(e) {
@@ -46,9 +46,9 @@ export function updatePageTitle() {
46
46
  if (ctx.projectName && sessionTitle) {
47
47
  document.title = sessionTitle + " - " + ctx.projectName;
48
48
  } else if (ctx.projectName) {
49
- document.title = ctx.projectName + " - Clay";
49
+ document.title = ctx.projectName + " - Clay Studio";
50
50
  } else {
51
- document.title = "Clay";
51
+ document.title = "Clay Studio";
52
52
  }
53
53
  }
54
54