clay-server 2.15.2 → 2.16.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.
@@ -83,6 +83,8 @@
83
83
  <p id="hub-greeting-date"></p>
84
84
  </div>
85
85
 
86
+ <div id="home-hub-mates" class="home-hub-mates hidden"></div>
87
+
86
88
  <div class="hub-columns">
87
89
  <div class="hub-card hub-upcoming">
88
90
  <div class="hub-card-header">
@@ -198,6 +200,7 @@
198
200
  <img id="mate-sidebar-avatar" class="mate-sidebar-avatar" alt="">
199
201
  <span id="mate-sidebar-name" class="mate-sidebar-name"></span>
200
202
  <div id="mate-sidebar-seed-tooltip" class="mate-seed-tooltip hidden"></div>
203
+ <button id="mate-sidebar-toggle-btn" class="sidebar-collapse-btn" title="Collapse sidebar"><i data-lucide="panel-left-close"></i></button>
201
204
  </div>
202
205
  <div id="mate-sidebar-tools">
203
206
  <button id="mate-knowledge-btn"><i data-lucide="book-open"></i> <span>Knowledge</span><span id="mate-knowledge-count" class="sidebar-badge hidden"></span></button>
@@ -246,8 +249,12 @@
246
249
  <button id="mate-knowledge-editor-save" class="mate-knowledge-header-btn" disabled title="Save"><i data-lucide="save"></i></button>
247
250
  <button id="mate-knowledge-close-btn" class="mate-knowledge-header-btn" title="Close"><i data-lucide="x"></i></button>
248
251
  </div>
252
+ <div class="mate-knowledge-tab-bar">
253
+ <button class="mate-knowledge-tab-btn active" data-tab="edit">Edit</button>
254
+ <button class="mate-knowledge-tab-btn" data-tab="preview">Preview</button>
255
+ </div>
249
256
  <div class="mate-knowledge-body">
250
- <div class="mate-knowledge-editor-pane">
257
+ <div class="mate-knowledge-editor-pane mobile-active">
251
258
  <div class="mate-editor-highlight-wrap">
252
259
  <pre class="mate-editor-highlight-pre" aria-hidden="true"><code id="mate-knowledge-editor-highlight" class="language-markdown"></code></pre>
253
260
  <textarea id="mate-knowledge-editor-content" placeholder="Select a file from the sidebar..." spellcheck="false"></textarea>
@@ -265,10 +272,19 @@
265
272
  <div class="title-bar-content">
266
273
  <div id="header-left">
267
274
  <button id="sidebar-expand-btn" title="Open sidebar"><i data-lucide="panel-left-open"></i></button>
275
+ <div id="mate-collapsed-info" class="mate-collapsed-info">
276
+ <img id="mate-collapsed-avatar" class="mate-collapsed-avatar" alt="">
277
+ <span id="mate-collapsed-name" class="mate-collapsed-name"></span>
278
+ </div>
268
279
  <button id="hamburger-btn" aria-label="Toggle sidebar" title="Toggle sidebar"><i data-lucide="menu"></i></button>
269
280
  <span class="header-title" id="header-title">Connecting...</span>
270
281
  <button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
271
282
  <button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
283
+ <div id="mate-mobile-title" class="mate-mobile-title hidden">
284
+ <button id="mate-mobile-back" class="mate-mobile-back" type="button" aria-label="Back"><i data-lucide="chevron-left"></i></button>
285
+ <img id="mate-mobile-avatar" class="mate-mobile-avatar" alt="">
286
+ <span id="mate-mobile-name" class="mate-mobile-name"></span>
287
+ </div>
272
288
  </div>
273
289
  <div id="todo-sticky" class="hidden"></div>
274
290
  <div id="ralph-sticky" class="hidden"></div>
@@ -444,11 +460,11 @@
444
460
 
445
461
  <!-- Mobile bottom tab bar -->
446
462
  <div id="mobile-tab-bar">
447
- <button class="mobile-tab" data-tab="projects"><i data-lucide="folder-kanban"></i><span class="mobile-tab-label">Projects</span></button>
448
- <button class="mobile-tab" data-tab="sessions"><i data-lucide="messages-square"></i><span class="mobile-tab-label">Sessions</span></button>
449
- <button class="mobile-tab-home" id="mobile-home-btn"><img src="/icon-banded-76.png" alt="Clay" class="mobile-home-icon"></button>
450
- <button class="mobile-tab" data-tab="files"><i data-lucide="folder-tree"></i><span class="mobile-tab-label">Files</span></button>
451
- <button class="mobile-tab" data-tab="terminal"><i data-lucide="square-terminal"></i><span id="mobile-terminal-count" class="mobile-tab-badge hidden"></span><span class="mobile-tab-label">Terminal</span></button>
463
+ <button class="mobile-tab" data-tab="chat" id="mob-tab-chat"><i data-lucide="message-square"></i><span class="mobile-tab-label">Chat</span></button>
464
+ <button class="mobile-tab" data-tab="search" id="mob-tab-search"><i data-lucide="search"></i><span class="mobile-tab-label">Search</span></button>
465
+ <button class="mobile-tab-home" id="mobile-home-btn"><img src="/icon-banded-76.png" alt="Home" class="mobile-home-icon"></button>
466
+ <button class="mobile-tab" data-tab="tools" id="mob-tab-tools"><i data-lucide="wrench"></i><span class="mobile-tab-label">Tools</span></button>
467
+ <button class="mobile-tab" data-tab="settings" id="mob-tab-settings"><i data-lucide="settings"></i><span class="mobile-tab-label">Settings</span></button>
452
468
  </div>
453
469
 
454
470
  <div id="file-viewer" class="hidden">
@@ -481,6 +497,16 @@
481
497
  <div class="project-settings-nav">
482
498
  <div class="project-settings-nav-inner">
483
499
  <div class="server-settings-nav-header" id="ps-nav-title">-</div>
500
+ <select id="ps-nav-dropdown" class="settings-nav-dropdown">
501
+ <optgroup label="General">
502
+ <option value="profile" selected>Profile</option>
503
+ <option value="defaults">Model</option>
504
+ </optgroup>
505
+ <optgroup label="Config">
506
+ <option value="instructions">Instructions</option>
507
+ <option value="environment">Environment</option>
508
+ </optgroup>
509
+ </select>
484
510
  <div class="server-settings-nav-items">
485
511
  <div class="settings-nav-category">General</div>
486
512
  <button class="settings-nav-item active" data-section="profile"><span>Profile</span></button>
@@ -662,6 +688,11 @@
662
688
  <button class="term-key term-key-arrow" data-key="down">&#9660;</button>
663
689
  <button class="term-key term-key-arrow" data-key="left">&#9664;</button>
664
690
  <button class="term-key term-key-arrow" data-key="right">&#9654;</button>
691
+ <span class="term-key-spacer"></span>
692
+ <button class="term-key term-key-toggle" data-key="alt">Alt</button>
693
+ <button class="term-key" data-key="pipe">|</button>
694
+ <button class="term-key" data-key="slash">/</button>
695
+ <button class="term-key" data-key="tilde">~</button>
665
696
  </div>
666
697
  <div id="terminal-body"></div>
667
698
  </div>
@@ -759,6 +790,7 @@
759
790
  <div class="settings-nav-category">Config</div>
760
791
  <button class="settings-nav-item" data-section="claudemd"><span>Instructions</span></button>
761
792
  <button class="settings-nav-item" data-section="environment"><span>Environment</span></button>
793
+ <button class="settings-nav-item" data-section="storage"><span>Storage</span></button>
762
794
  <div class="settings-nav-separator settings-admin-only"></div>
763
795
  <div class="settings-nav-category settings-admin-only">Admin</div>
764
796
  <button class="settings-nav-item settings-admin-only" data-section="admin-users"><span>Users</span></button>
@@ -988,6 +1020,25 @@
988
1020
  <!-- rows rendered by JS -->
989
1021
  </div>
990
1022
  </div>
1023
+ <div class="server-settings-section" data-section="storage">
1024
+ <h2>Storage</h2>
1025
+ <div class="settings-card">
1026
+ <label class="settings-toggle-row">
1027
+ <div>
1028
+ <span class="settings-label">Image retention</span>
1029
+ <div class="settings-hint">Automatically delete chat images older than this period.</div>
1030
+ </div>
1031
+ <select id="settings-image-retention" class="settings-select">
1032
+ <option value="0">Forever</option>
1033
+ <option value="3">3 days</option>
1034
+ <option value="7" selected>7 days</option>
1035
+ <option value="14">14 days</option>
1036
+ <option value="30">30 days</option>
1037
+ <option value="90">90 days</option>
1038
+ </select>
1039
+ </label>
1040
+ </div>
1041
+ </div>
991
1042
  <div class="server-settings-section hidden" data-section="keep-awake" id="settings-keep-awake-section">
992
1043
  <h2>Keep Awake</h2>
993
1044
  <div class="settings-card">
@@ -0,0 +1,74 @@
1
+ // Long-press to synthesize contextmenu events on touch devices.
2
+ // All existing contextmenu listeners automatically work with this.
3
+
4
+ var LONG_PRESS_MS = 500;
5
+ var MOVE_THRESHOLD = 10;
6
+
7
+ var _timer = null;
8
+ var _startX = 0;
9
+ var _startY = 0;
10
+ var _fired = false;
11
+ var _targetEl = null;
12
+
13
+ function cancelTimer() {
14
+ if (_timer) {
15
+ clearTimeout(_timer);
16
+ _timer = null;
17
+ }
18
+ }
19
+
20
+ export function initLongPress() {
21
+ if (!("ontouchstart" in window)) return;
22
+
23
+ document.addEventListener("touchstart", function (e) {
24
+ if (e.touches.length !== 1) {
25
+ cancelTimer();
26
+ return;
27
+ }
28
+ var touch = e.touches[0];
29
+ _startX = touch.clientX;
30
+ _startY = touch.clientY;
31
+ _fired = false;
32
+ _targetEl = e.target;
33
+
34
+ _timer = setTimeout(function () {
35
+ _timer = null;
36
+ _fired = true;
37
+
38
+ // Haptic feedback if available
39
+ if (navigator.vibrate) navigator.vibrate(30);
40
+
41
+ var evt = new MouseEvent("contextmenu", {
42
+ bubbles: true,
43
+ cancelable: true,
44
+ clientX: _startX,
45
+ clientY: _startY
46
+ });
47
+ _targetEl.dispatchEvent(evt);
48
+ }, LONG_PRESS_MS);
49
+ }, { passive: true });
50
+
51
+ document.addEventListener("touchmove", function (e) {
52
+ if (!_timer) return;
53
+ var touch = e.touches[0];
54
+ var dx = touch.clientX - _startX;
55
+ var dy = touch.clientY - _startY;
56
+ if (dx * dx + dy * dy > MOVE_THRESHOLD * MOVE_THRESHOLD) {
57
+ cancelTimer();
58
+ }
59
+ }, { passive: true });
60
+
61
+ document.addEventListener("touchend", function (e) {
62
+ cancelTimer();
63
+ // Suppress tap/click after a long-press fired
64
+ if (_fired) {
65
+ _fired = false;
66
+ e.preventDefault();
67
+ }
68
+ }, { passive: false });
69
+
70
+ document.addEventListener("touchcancel", function () {
71
+ cancelTimer();
72
+ _fired = false;
73
+ }, { passive: true });
74
+ }
@@ -161,6 +161,36 @@ export function initMateKnowledge(mateWsGetter) {
161
161
  editorContentEl.addEventListener("scroll", syncHighlightScroll);
162
162
  initFormatPopover(editorContentEl);
163
163
  }
164
+
165
+ // --- Mobile tab switching (Edit / Preview) ---
166
+ var tabBar = document.querySelector(".mate-knowledge-tab-bar");
167
+ if (tabBar) {
168
+ var tabBtns = tabBar.querySelectorAll(".mate-knowledge-tab-btn");
169
+ var editorPane = document.querySelector(".mate-knowledge-editor-pane");
170
+ var previewPane = document.querySelector(".mate-knowledge-preview-pane");
171
+ for (var ti = 0; ti < tabBtns.length; ti++) {
172
+ (function (btn) {
173
+ btn.addEventListener("click", function () {
174
+ for (var j = 0; j < tabBtns.length; j++) {
175
+ tabBtns[j].classList.remove("active");
176
+ }
177
+ btn.classList.add("active");
178
+ var tab = btn.dataset.tab;
179
+ if (editorPane && previewPane) {
180
+ if (tab === "edit") {
181
+ editorPane.classList.add("mobile-active");
182
+ previewPane.classList.remove("mobile-active");
183
+ } else {
184
+ editorPane.classList.remove("mobile-active");
185
+ previewPane.classList.add("mobile-active");
186
+ // Ensure preview is up to date
187
+ if (typeof updatePreview === "function") updatePreview();
188
+ }
189
+ }
190
+ });
191
+ })(tabBtns[ti]);
192
+ }
193
+ }
164
194
  }
165
195
 
166
196
  // --- Mode switching ---
@@ -139,11 +139,16 @@ export function showMateSidebar(mateId, mateData) {
139
139
 
140
140
  var mateColor = profile.avatarColor || mateData.avatarColor || "#7c3aed";
141
141
 
142
- if (avatarEl) {
143
- avatarEl.src = "https://api.dicebear.com/9.x/" + encodeURIComponent(avatarStyle) + "/svg?seed=" + encodeURIComponent(avatarSeed) + "&size=32";
144
- }
142
+ var avatarUrl = "https://api.dicebear.com/9.x/" + encodeURIComponent(avatarStyle) + "/svg?seed=" + encodeURIComponent(avatarSeed) + "&size=32";
143
+ if (avatarEl) avatarEl.src = avatarUrl;
145
144
  if (nameEl) nameEl.textContent = displayName;
146
145
 
146
+ // Also populate collapsed header info
147
+ var collapsedAvatar = document.getElementById("mate-collapsed-avatar");
148
+ var collapsedName = document.getElementById("mate-collapsed-name");
149
+ if (collapsedAvatar) collapsedAvatar.src = avatarUrl;
150
+ if (collapsedName) collapsedName.textContent = displayName;
151
+
147
152
  // Apply mate color to sidebar
148
153
  var headerEl = columnEl.querySelector(".mate-sidebar-header");
149
154
  if (headerEl) headerEl.style.background = mateColor;
@@ -375,14 +380,14 @@ function renderMateSessionItem(s) {
375
380
  el.className = "mate-session-item" + (s.active ? " active" : "");
376
381
  el.dataset.sessionId = s.id;
377
382
 
383
+ var processingDot = document.createElement("span");
384
+ processingDot.className = "session-processing";
385
+ if (!s.isProcessing) processingDot.style.display = "none";
386
+ el.appendChild(processingDot);
387
+
378
388
  var textSpan = document.createElement("span");
379
389
  textSpan.className = "mate-session-item-text";
380
- var html = "";
381
- if (s.isProcessing) {
382
- html += '<span class="mate-session-processing"></span>';
383
- }
384
- html += escapeHtml(s.title || "New Session");
385
- textSpan.innerHTML = html;
390
+ textSpan.textContent = s.title || "New Session";
386
391
  el.appendChild(textSpan);
387
392
 
388
393
  // Relative time
@@ -71,10 +71,20 @@ export function initNotifications(_ctx) {
71
71
  // --- Mobile viewport (iOS keyboard handling) ---
72
72
  if (window.visualViewport) {
73
73
  var layout = $("layout");
74
+ var mobileTabBar = document.getElementById("mobile-tab-bar");
74
75
  function onViewportChange() {
75
76
  layout.style.height = window.visualViewport.height + "px";
76
77
  document.documentElement.scrollTop = 0;
77
78
  ctx.scrollToBottom();
79
+ // Hide tab bar when software keyboard is open
80
+ if (mobileTabBar) {
81
+ var keyboardOpen = window.visualViewport.height < window.innerHeight * 0.75;
82
+ if (keyboardOpen) {
83
+ mobileTabBar.classList.add("keyboard-hidden");
84
+ } else {
85
+ mobileTabBar.classList.remove("keyboard-hidden");
86
+ }
87
+ }
78
88
  }
79
89
  window.visualViewport.addEventListener("resize", onViewportChange);
80
90
  window.visualViewport.addEventListener("scroll", onViewportChange);
@@ -33,6 +33,14 @@ export function initProjectSettings(appCtx, emojiCategories) {
33
33
  });
34
34
  }
35
35
 
36
+ // Mobile dropdown nav
37
+ var psNavDropdown = document.getElementById("ps-nav-dropdown");
38
+ if (psNavDropdown) {
39
+ psNavDropdown.addEventListener("change", function () {
40
+ switchSection(psNavDropdown.value);
41
+ });
42
+ }
43
+
36
44
  // Close button
37
45
  var closeBtn = document.getElementById("project-settings-close");
38
46
  if (closeBtn) {
@@ -201,6 +209,12 @@ function switchSection(name) {
201
209
  sections[j].classList.toggle("active", active2);
202
210
  }
203
211
 
212
+ // Sync mobile dropdown
213
+ var psNavDropdown = document.getElementById("ps-nav-dropdown");
214
+ if (psNavDropdown && psNavDropdown.value !== name) {
215
+ psNavDropdown.value = name;
216
+ }
217
+
204
218
  // Lazy-load section data
205
219
  if (name === "defaults") populateDefaults();
206
220
  if (name === "instructions") loadInstructions();
@@ -155,6 +155,17 @@ export function initServerSettings(appCtx) {
155
155
  });
156
156
  }
157
157
 
158
+ // Image retention select
159
+ var imageRetentionSelect = document.getElementById("settings-image-retention");
160
+ if (imageRetentionSelect) {
161
+ imageRetentionSelect.addEventListener("change", function () {
162
+ var ws = ctx.ws;
163
+ if (ws && ws.readyState === 1) {
164
+ ws.send(JSON.stringify({ type: "set_image_retention", days: parseInt(this.value, 10) }));
165
+ }
166
+ });
167
+ }
168
+
158
169
  // Global CLAUDE.md: save button
159
170
  var ssClaudeMdSave = document.getElementById("ss-claudemd-save");
160
171
  if (ssClaudeMdSave) {
@@ -468,6 +479,12 @@ export function updateDaemonConfig(config) {
468
479
  var keepAwakeToggle = document.getElementById("settings-keep-awake");
469
480
  if (keepAwakeToggle) keepAwakeToggle.checked = !!config.keepAwake;
470
481
 
482
+ // Image retention
483
+ var imageRetentionSelect = document.getElementById("settings-image-retention");
484
+ if (imageRetentionSelect && config.imageRetentionDays !== undefined) {
485
+ imageRetentionSelect.value = String(config.imageRetentionDays);
486
+ }
487
+
471
488
  // Early Access toggle
472
489
  var channelToggle = document.getElementById("settings-update-channel");
473
490
  if (channelToggle) {