clay-server 2.29.4-beta.2 → 2.29.4

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.
@@ -1135,13 +1135,8 @@ export function processMessage(msg) {
1135
1135
  var _s1 = store.getState();
1136
1136
  if (fromId && fromId !== _s1.myUserId) {
1137
1137
  _s1.dmUnread[fromId] = (_s1.dmUnread[fromId] || 0) + 1;
1138
- // Only re-render the full strip if the sender isn't already visible
1139
- // (e.g. non-favorited user sending their first message). Otherwise
1140
- // just update the badge in-place to avoid a full DOM rebuild flicker.
1141
- var existingEl = document.querySelector('.icon-strip-user[data-user-id="' + fromId + '"]');
1142
- if (!existingEl) {
1143
- renderUserStrip(_s1.cachedAllUsers, _s1.cachedOnlineIds, _s1.myUserId, _s1.cachedDmFavorites, _s1.cachedDmConversations, _s1.dmUnread, _s1.dmRemovedUsers, _s1.cachedMatesList);
1144
- }
1138
+ // Re-render strip so non-favorited sender appears
1139
+ renderUserStrip(_s1.cachedAllUsers, _s1.cachedOnlineIds, _s1.myUserId, _s1.cachedDmFavorites, _s1.cachedDmConversations, _s1.dmUnread, _s1.dmRemovedUsers, _s1.cachedMatesList);
1145
1140
  updateDmBadge(fromId, _s1.dmUnread[fromId]);
1146
1141
  }
1147
1142
  }
@@ -181,23 +181,40 @@ export function setCachedRemovedProjects(v) { cachedRemovedProjects = v; }
181
181
 
182
182
  export function updateProjectList(msg) {
183
183
  if (typeof msg.projectCount === "number") cachedProjectCount = msg.projectCount;
184
- if (msg.projects) cachedProjects = msg.projects;
184
+
185
+ // Compare projects before caching to detect actual changes
186
+ var projectsChanged = false;
187
+ if (msg.projects) {
188
+ var projectsJson = JSON.stringify(msg.projects);
189
+ if (projectsJson !== _lastProjectsJson) {
190
+ projectsChanged = true;
191
+ _lastProjectsJson = projectsJson;
192
+ }
193
+ cachedProjects = msg.projects;
194
+ }
185
195
  if (msg.removedProjects) cachedRemovedProjects = msg.removedProjects;
186
196
  else if (msg.removedProjects === undefined) { /* keep cached */ }
187
197
  else cachedRemovedProjects = [];
188
- var count = cachedProjectCount || 0;
189
- renderProjectList();
190
- var projectHint = _ctx.$("project-hint");
191
- if (count === 1 && projectHint) {
192
- try {
193
- if (!localStorage.getItem("clay-project-hint-dismissed")) {
194
- projectHint.classList.remove("hidden");
195
- }
196
- } catch (e) {}
197
- } else if (projectHint) {
198
- projectHint.classList.add("hidden");
198
+
199
+ // Only re-render project strip + title bar if data or active slug changed
200
+ var slugChanged = _ctx.currentSlug !== _lastRenderedSlug;
201
+ if (projectsChanged || slugChanged) {
202
+ _lastRenderedSlug = _ctx.currentSlug;
203
+ var count = cachedProjectCount || 0;
204
+ renderProjectList();
205
+ var projectHint = _ctx.$("project-hint");
206
+ if (count === 1 && projectHint) {
207
+ try {
208
+ if (!localStorage.getItem("clay-project-hint-dismissed")) {
209
+ projectHint.classList.remove("hidden");
210
+ }
211
+ } catch (e) {}
212
+ } else if (projectHint) {
213
+ projectHint.classList.add("hidden");
214
+ }
199
215
  }
200
- // Update topbar with server-wide presence
216
+
217
+ // Update topbar with server-wide presence (renderTopbarPresence has its own guard)
201
218
  if (msg.serverUsers) {
202
219
  var newOnlineIds = msg.serverUsers.map(function (u) { return u.id; });
203
220
  var prevOnlineIds = _ctx.cachedOnlineIds || [];
@@ -211,12 +228,24 @@ export function updateProjectList(msg) {
211
228
  }
212
229
  }
213
230
  }
214
- // Update user strip (DM targets) in icon strip
231
+
232
+ // Update user strip (DM targets) - only if user data actually changed
215
233
  if (msg.allUsers) {
234
+ var allUsersJson = JSON.stringify(msg.allUsers);
235
+ var dmFavJson = msg.dmFavorites ? JSON.stringify(msg.dmFavorites) : _lastDmFavJson;
236
+ var dmConvJson = msg.dmConversations ? JSON.stringify(msg.dmConversations) : _lastDmConvJson;
237
+ var userDataChanged = allUsersJson !== _lastAllUsersJson || dmFavJson !== _lastDmFavJson || dmConvJson !== _lastDmConvJson;
238
+
216
239
  _ctx.setCachedAllUsers(msg.allUsers);
217
240
  if (msg.dmFavorites) _ctx.setCachedDmFavorites(msg.dmFavorites);
218
241
  if (msg.dmConversations) _ctx.setCachedDmConversations(msg.dmConversations);
219
- _ctx.renderUserStrip(msg.allUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
242
+
243
+ if (userDataChanged) {
244
+ _lastAllUsersJson = allUsersJson;
245
+ _lastDmFavJson = dmFavJson;
246
+ _lastDmConvJson = dmConvJson;
247
+ _ctx.renderUserStrip(msg.allUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
248
+ }
220
249
  if (document.body.classList.contains("mate-dm-active") || document.body.classList.contains("wide-view")) {
221
250
  var refreshedMyUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
222
251
  if (refreshedMyUser) {
@@ -240,6 +269,11 @@ export function updateProjectList(msg) {
240
269
  }
241
270
 
242
271
  var _lastTopbarUserIds = [];
272
+ var _lastProjectsJson = "";
273
+ var _lastRenderedSlug = null;
274
+ var _lastAllUsersJson = "";
275
+ var _lastDmFavJson = "";
276
+ var _lastDmConvJson = "";
243
277
  export function renderTopbarPresence(serverUsers) {
244
278
  var countEl = document.getElementById("client-count");
245
279
  if (!countEl) return;
@@ -311,11 +345,7 @@ export function renderProjectList() {
311
345
  }
312
346
  }
313
347
  }
314
- // Clear stale socket status from all project dots, then re-apply to active
315
- var allDots = document.querySelectorAll("#icon-strip-projects .icon-strip-status");
316
- for (var di = 0; di < allDots.length; di++) {
317
- allDots[di].classList.remove("connected", "processing");
318
- }
348
+ // Re-apply current socket status to the active icon's dot
319
349
  var dot = _ctx.getStatusDot();
320
350
  if (dot) {
321
351
  if (_ctx.connected && _ctx.processing) { dot.classList.add("connected"); dot.classList.add("processing"); }
@@ -1170,62 +1170,12 @@ function showWorktreeModal(parentSlug, parentName) {
1170
1170
 
1171
1171
  // --- Render icon strip ---
1172
1172
 
1173
- // Build a fingerprint string for the project list so we can detect when only
1174
- // the active slug changed (no structural DOM rebuild needed).
1175
- function projectListFingerprint(projects) {
1176
- var parts = [];
1177
- for (var i = 0; i < projects.length; i++) {
1178
- var p = projects[i];
1179
- parts.push(p.slug + ":" + (p.icon || "") + ":" + (p.isProcessing ? 1 : 0) + ":" + (p.unread || 0) + ":" + (p.pendingPermissions || 0) + ":" + (p.isWorktree ? 1 : 0) + ":" + (p.parentSlug || "") + ":" + (p.worktreeAccessible !== false ? 1 : 0));
1180
- }
1181
- return parts.join("|");
1182
- }
1183
- var _lastStripFingerprint = "";
1184
-
1185
1173
  export function renderIconStrip(projects, currentSlug) {
1186
1174
  cachedProjectList = projects;
1187
1175
  cachedCurrentSlug = currentSlug;
1188
1176
 
1189
1177
  var container = document.getElementById("icon-strip-projects");
1190
1178
  if (!container) return;
1191
-
1192
- // Fast path: if only the active slug changed (project list unchanged),
1193
- // just toggle active classes instead of a full DOM rebuild.
1194
- var fp = projectListFingerprint(projects);
1195
- if (fp === _lastStripFingerprint && container.children.length > 0) {
1196
- var currentDmUserId2 = _ctx.getCurrentDmUserId ? _ctx.getCurrentDmUserId() : null;
1197
- var items = container.querySelectorAll(".icon-strip-item");
1198
- for (var fi = 0; fi < items.length; fi++) {
1199
- var isNowActive = items[fi].dataset.slug === currentSlug && !currentDmUserId2;
1200
- items[fi].classList.toggle("active", isNowActive);
1201
- // Clear stale socket status dot from inactive items
1202
- if (!isNowActive) {
1203
- var statusDot = items[fi].querySelector(".icon-strip-status");
1204
- if (statusDot) statusDot.classList.remove("connected", "processing");
1205
- }
1206
- // Update unread badge visibility (active items hide their badge)
1207
- var badge = items[fi].querySelector(".icon-strip-project-badge");
1208
- if (badge && badge.classList.contains("has-unread") && isNowActive) {
1209
- badge.textContent = "";
1210
- badge.classList.remove("has-unread");
1211
- }
1212
- // Update pending permission glow
1213
- if (isNowActive) items[fi].classList.remove("has-pending-perm");
1214
- }
1215
- // Update home icon
1216
- var homeIcon2 = document.querySelector(".icon-strip-home");
1217
- if (homeIcon2) {
1218
- if ((!currentSlug || projects.length === 0) && !currentDmUserId2) {
1219
- homeIcon2.classList.add("active");
1220
- } else {
1221
- homeIcon2.classList.remove("active");
1222
- }
1223
- }
1224
- renderProjectList(projects, currentSlug);
1225
- return;
1226
- }
1227
- _lastStripFingerprint = fp;
1228
-
1229
1179
  container.innerHTML = "";
1230
1180
 
1231
1181
  var currentDmUserId = _ctx.getCurrentDmUserId ? _ctx.getCurrentDmUserId() : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.29.4-beta.2",
3
+ "version": "2.29.4",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",