clay-server 2.47.0-beta.4 → 2.47.0-beta.6

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.
@@ -105,7 +105,6 @@ function attachConnection(ctx) {
105
105
  }
106
106
 
107
107
  var loopState = _loop.loopState;
108
- var loopRegistry = _loop.loopRegistry;
109
108
 
110
109
  // Resume loop if server restarted mid-execution (deferred so client gets initial state first)
111
110
  if (loopState._needsResume) {
@@ -179,32 +178,13 @@ function attachConnection(ctx) {
179
178
  if (_mcp) _mcp.sendConnectionState(ws);
180
179
  if (_notifications) _notifications.sendConnectionState(ws, sendTo);
181
180
 
182
- // Session list (filtered for access control)
181
+ // Session list (filtered for access control). Uses the same mapper as
182
+ // broadcastSessionList: a hand-rolled copy here silently drifts and
183
+ // drops fields (the connect-time list was missing vendor/spawn/mode,
184
+ // so vendor badges showed Claude until the next broadcast).
183
185
  sendTo(ws, {
184
186
  type: "session_list",
185
- sessions: allSessions.map(function (s) {
186
- var loop = s.loop ? Object.assign({}, s.loop) : null;
187
- if (loop && loop.loopId && loopRegistry) {
188
- var rec = loopRegistry.getById(loop.loopId);
189
- if (rec) {
190
- if (rec.name) loop.name = rec.name;
191
- if (rec.source) loop.source = rec.source;
192
- }
193
- }
194
- return {
195
- id: s.localId,
196
- cliSessionId: s.cliSessionId || null,
197
- title: s.title || "New Session",
198
- active: s.localId === sm.activeSessionId,
199
- isProcessing: s.isProcessing,
200
- lastActivity: s.lastActivity || s.createdAt || 0,
201
- loop: loop,
202
- ownerId: s.ownerId || null,
203
- sessionVisibility: s.sessionVisibility || "shared",
204
- bookmarked: !!s.bookmarked,
205
- favoriteOrder: typeof s.favoriteOrder === "number" ? s.favoriteOrder : null,
206
- };
207
- }),
187
+ sessions: allSessions.map(function (s) { return sm.mapSessionForClient(s); }),
208
188
  });
209
189
 
210
190
  // Restore active session for this client from server-side presence
@@ -676,10 +676,13 @@ function attachHTTP(ctx) {
676
676
  defBr = hrRef.replace(/^origin\//, "");
677
677
  } catch (e) {}
678
678
  res.writeHead(200, { "Content-Type": "application/json" });
679
- res.end(JSON.stringify({ branches: brList, defaultBranch: defBr }));
679
+ res.end(JSON.stringify({ branches: brList, defaultBranch: defBr, isGitRepo: true }));
680
680
  } catch (e) {
681
+ // git failed: not a repository (or git missing). The branch chip
682
+ // hides itself on isGitRepo:false; the fallback branch list keeps
683
+ // the worktree modal from crashing if it is somehow reached.
681
684
  res.writeHead(200, { "Content-Type": "application/json" });
682
- res.end(JSON.stringify({ branches: ["main"], defaultBranch: "main" }));
685
+ res.end(JSON.stringify({ branches: ["main"], defaultBranch: "main", isGitRepo: false }));
683
686
  }
684
687
  return true;
685
688
  }
package/lib/public/app.js CHANGED
@@ -42,6 +42,7 @@ import { initProfile, getProfileLang } from './modules/profile.js';
42
42
  import { initUserSettings } from './modules/user-settings.js';
43
43
  import { initToolPalettes } from './modules/tool-palette.js';
44
44
  import { initProjectSwitcher } from './modules/project-switcher.js';
45
+ import { initBranchSwitcher } from './modules/branch-switcher.js';
45
46
  import { initAdmin, checkAdminAccess } from './modules/admin.js';
46
47
  import { initSessionSearch, toggleSearch, closeSearch, isSearchOpen, handleFindInSessionResults, onHistoryPrepended as onSessionSearchHistoryPrepended } from './modules/session-search.js';
47
48
  import { initTooltips, registerTooltip } from './modules/tooltip.js';
@@ -85,7 +86,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
85
86
  var sendBtn = $("send-btn");
86
87
  function getStatusDot() {
87
88
  return document.querySelector("#icon-strip-projects .icon-strip-item.active .icon-strip-status") ||
88
- document.querySelector("#icon-strip-projects .icon-strip-wt-item.active .icon-strip-status") ||
89
89
  document.querySelector("#icon-strip-users .icon-strip-mate.active .icon-strip-status");
90
90
  }
91
91
  var headerTitleEl = $("header-title");
@@ -270,6 +270,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
270
270
  projectName: projectName,
271
271
  cwd: "",
272
272
  currentSlug: currentSlug,
273
+ projects: [],
274
+ homeHubVisible: false,
273
275
  currentProjectOwnerId: null,
274
276
  isOsUsers: false,
275
277
  skipPermsEnabled: false,
@@ -461,6 +463,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
461
463
  // keydown listener registers later — capture-phase ordering doesn't
462
464
  // matter here but it keeps related bootstrap steps adjacent.
463
465
  initProjectSwitcher();
466
+ initBranchSwitcher();
464
467
 
465
468
  // --- Connect overlay (animated ASCII logo) ---
466
469
  var asciiLogoCanvas = $("ascii-logo-canvas");
@@ -223,7 +223,6 @@
223
223
 
224
224
  /* Show dot only on active item or when processing */
225
225
  .icon-strip-item.active .icon-strip-status,
226
- .icon-strip-wt-item.active .icon-strip-status,
227
226
  .icon-strip-status.processing {
228
227
  opacity: 1;
229
228
  }
@@ -248,7 +247,6 @@
248
247
 
249
248
  /* Pending permission shake */
250
249
  .icon-strip-item.has-pending-perm,
251
- .icon-strip-wt-item.has-pending-perm,
252
250
  .icon-strip-mate.has-pending-perm {
253
251
  animation: permShake 2s ease-in-out infinite;
254
252
  }
@@ -1146,155 +1144,6 @@
1146
1144
  opacity: 1;
1147
1145
  }
1148
1146
 
1149
- /* --- Worktree folder groups --- */
1150
- .icon-strip-group {
1151
- display: flex;
1152
- flex-direction: column;
1153
- align-items: center;
1154
- position: relative;
1155
- }
1156
-
1157
- .icon-strip-group .folder-header {
1158
- position: relative;
1159
- }
1160
-
1161
- .icon-strip-group-chevron {
1162
- position: absolute;
1163
- bottom: 2px;
1164
- right: 2px;
1165
- width: 16px;
1166
- height: 16px;
1167
- border-radius: 50%;
1168
- background: var(--bg-alt);
1169
- border: 1px solid var(--border);
1170
- display: flex;
1171
- align-items: center;
1172
- justify-content: center;
1173
- color: var(--text-dimmer);
1174
- pointer-events: auto;
1175
- cursor: pointer;
1176
- transition: transform 0.15s ease, background 0.15s, border-color 0.15s;
1177
- z-index: 2;
1178
- line-height: 1;
1179
- }
1180
-
1181
- .icon-strip-group-chevron i,
1182
- .icon-strip-group-chevron svg {
1183
- width: 10px;
1184
- height: 10px;
1185
- }
1186
-
1187
- .icon-strip-group-chevron:hover {
1188
- background: var(--bg-hover, var(--bg-alt));
1189
- border-color: var(--text-dimmer);
1190
- color: var(--text-secondary);
1191
- }
1192
-
1193
- .icon-strip-group.collapsed .icon-strip-group-chevron {
1194
- background: var(--border);
1195
- }
1196
-
1197
- .icon-strip-group-items {
1198
- display: flex;
1199
- flex-direction: column;
1200
- align-items: center;
1201
- overflow: hidden;
1202
- transition: max-height 0.2s ease;
1203
- max-height: 500px;
1204
- border-left: 2px solid var(--border);
1205
- margin-left: 0;
1206
- padding-left: 0;
1207
- }
1208
-
1209
- .icon-strip-group.collapsed .icon-strip-group-items {
1210
- max-height: 0;
1211
- }
1212
-
1213
- /* Worktree items (smaller than regular project icons) */
1214
- .icon-strip-wt-item {
1215
- width: 48px;
1216
- height: 40px;
1217
- display: flex;
1218
- align-items: center;
1219
- justify-content: center;
1220
- cursor: pointer;
1221
- position: relative;
1222
- text-decoration: none;
1223
- color: var(--text-secondary);
1224
- transition: background 0.15s;
1225
- }
1226
-
1227
- .icon-strip-wt-item::before {
1228
- content: "";
1229
- position: absolute;
1230
- width: 30px;
1231
- height: 30px;
1232
- border-radius: 8px;
1233
- background: var(--bg-alt);
1234
- opacity: 0.7;
1235
- transition: opacity 0.15s, background 0.15s;
1236
- }
1237
-
1238
- .icon-strip-wt-item:hover::before {
1239
- opacity: 1;
1240
- }
1241
-
1242
- .icon-strip-wt-item.active::before {
1243
- background: var(--accent);
1244
- opacity: 1;
1245
- }
1246
-
1247
- .icon-strip-wt-item.active {
1248
- color: #fff;
1249
- }
1250
-
1251
- .icon-strip-wt-item .wt-branch-abbrev {
1252
- font-size: 11px;
1253
- font-weight: 600;
1254
- letter-spacing: -0.5px;
1255
- pointer-events: none;
1256
- position: relative;
1257
- z-index: 1;
1258
- }
1259
-
1260
- /* Disabled/inaccessible worktree */
1261
- .icon-strip-wt-item.wt-disabled {
1262
- opacity: 0.35;
1263
- cursor: not-allowed;
1264
- }
1265
-
1266
- .icon-strip-wt-item.wt-disabled::after {
1267
- content: "\1F6AB";
1268
- position: absolute;
1269
- font-size: 14px;
1270
- z-index: 3;
1271
- filter: grayscale(0.3);
1272
- }
1273
-
1274
- /* Group add button */
1275
- .icon-strip-group-add {
1276
- width: 30px;
1277
- height: 30px;
1278
- border-radius: 8px;
1279
- border: 1.5px dashed var(--border);
1280
- background: transparent;
1281
- color: var(--text-dimmer);
1282
- font-size: 16px;
1283
- font-weight: 400;
1284
- cursor: pointer;
1285
- display: flex;
1286
- align-items: center;
1287
- justify-content: center;
1288
- margin: 2px 0 4px 0;
1289
- transition: border-color 0.15s, color 0.15s, background 0.15s;
1290
- }
1291
-
1292
- .icon-strip-group-add:hover {
1293
- border-color: var(--accent);
1294
- color: var(--accent);
1295
- background: rgba(var(--accent-rgb, 124, 58, 237), 0.08);
1296
- }
1297
-
1298
1147
  /* --- Worktree creation modal --- */
1299
1148
  .wt-modal-overlay {
1300
1149
  position: fixed;
@@ -1409,45 +1258,6 @@ select.wt-modal-input {
1409
1258
  cursor: not-allowed;
1410
1259
  }
1411
1260
 
1412
- /* --- Mobile worktree folder --- */
1413
- .mobile-project-folder {
1414
- display: flex;
1415
- flex-direction: column;
1416
- }
1417
-
1418
- .mobile-project-item.wt-item {
1419
- padding-left: 28px;
1420
- opacity: 0.85;
1421
- }
1422
-
1423
- .mobile-project-item.wt-disabled {
1424
- opacity: 0.35;
1425
- cursor: not-allowed;
1426
- }
1427
-
1428
- .mobile-folder-chevron {
1429
- font-size: 8px;
1430
- color: var(--text-dimmer);
1431
- margin-left: auto;
1432
- cursor: pointer;
1433
- transition: transform 0.15s ease;
1434
- padding: 4px;
1435
- }
1436
-
1437
- .mobile-project-folder.collapsed .mobile-folder-chevron {
1438
- transform: rotate(-90deg);
1439
- }
1440
-
1441
- .mobile-folder-items {
1442
- overflow: hidden;
1443
- transition: max-height 0.2s ease;
1444
- max-height: 500px;
1445
- }
1446
-
1447
- .mobile-project-folder.collapsed .mobile-folder-items {
1448
- max-height: 0;
1449
- }
1450
-
1451
1261
  /* --- Per-project presence avatars --- */
1452
1262
  /* --- Mobile: hide icon strip --- */
1453
1263
  /* --- Skeleton loading placeholders --- */
@@ -160,6 +160,10 @@
160
160
  background: rgba(var(--overlay-rgb), 0.06);
161
161
  }
162
162
 
163
+ .mobile-project-item.has-pending-perm {
164
+ animation: permShake 2s ease-in-out infinite;
165
+ }
166
+
163
167
  .mobile-project-abbrev {
164
168
  width: 32px;
165
169
  height: 32px;
@@ -438,6 +442,22 @@
438
442
  object-fit: cover;
439
443
  }
440
444
 
445
+ /* No hover on touch: rest state stays dimmed, the active session shows color. */
446
+ .mobile-session-vendor-icon {
447
+ width: 14px;
448
+ height: 14px;
449
+ flex-shrink: 0;
450
+ border-radius: 3px;
451
+ object-fit: cover;
452
+ margin-right: 8px;
453
+ opacity: 0.5;
454
+ filter: grayscale(0.9) brightness(0.9);
455
+ }
456
+ .mobile-session-item.active .mobile-session-vendor-icon {
457
+ opacity: 1;
458
+ filter: none;
459
+ }
460
+
441
461
  /* --- Chat filter bar (horizontal scroll chips) --- */
442
462
  .mobile-chat-filter-bar {
443
463
  display: flex;
@@ -835,6 +835,25 @@
835
835
  contain: size layout;
836
836
  }
837
837
 
838
+ /* Vendor badge stays nearly monochrome at rest so a long session list does
839
+ not read as a row of logos; hover or the active session restores color. */
840
+ .session-vendor-icon {
841
+ width: 13px;
842
+ height: 13px;
843
+ border-radius: 3px;
844
+ object-fit: cover;
845
+ vertical-align: -2px;
846
+ margin-right: 5px;
847
+ opacity: 0.45;
848
+ filter: grayscale(0.9) brightness(0.9);
849
+ transition: opacity 0.15s, filter 0.15s;
850
+ }
851
+ .session-item:hover .session-vendor-icon,
852
+ .session-item.active .session-vendor-icon {
853
+ opacity: 1;
854
+ filter: none;
855
+ }
856
+
838
857
  .session-item-text {
839
858
  flex: 1;
840
859
  overflow: hidden;
@@ -272,6 +272,175 @@
272
272
  transform: rotate(180deg);
273
273
  }
274
274
 
275
+ .branch-chip {
276
+ display: inline-flex;
277
+ align-items: center;
278
+ gap: 4px;
279
+ min-width: 0;
280
+ max-width: 132px;
281
+ /* Optical alignment: the pill reads slightly high next to the 17px/800
282
+ project name, so nudge it down within the centered flex row. */
283
+ margin-top: 2px;
284
+ padding: 4px 7px;
285
+ border: 1px solid var(--border);
286
+ border-radius: 999px;
287
+ background: var(--bg-alt);
288
+ color: var(--text-secondary);
289
+ font: 600 11px/1.2 "Pretendard", system-ui, sans-serif;
290
+ cursor: pointer;
291
+ flex-shrink: 1;
292
+ }
293
+
294
+ .branch-chip.hidden,
295
+ .branch-chip-menu.hidden {
296
+ display: none;
297
+ }
298
+
299
+ .branch-chip:hover,
300
+ .branch-chip.open {
301
+ border-color: var(--text-dimmer);
302
+ color: var(--text);
303
+ }
304
+
305
+ .branch-chip > svg {
306
+ width: 12px;
307
+ height: 12px;
308
+ flex-shrink: 0;
309
+ }
310
+
311
+ #branch-chip-label {
312
+ overflow: hidden;
313
+ text-overflow: ellipsis;
314
+ white-space: nowrap;
315
+ }
316
+
317
+ .branch-chip-chevron {
318
+ transition: transform 0.15s;
319
+ }
320
+
321
+ .branch-chip.open .branch-chip-chevron {
322
+ transform: rotate(180deg);
323
+ }
324
+
325
+ .branch-chip-menu {
326
+ position: fixed;
327
+ z-index: 10020;
328
+ width: 250px;
329
+ padding: 6px;
330
+ border: 1px solid var(--border);
331
+ border-radius: 10px;
332
+ background: var(--bg);
333
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
334
+ }
335
+
336
+ .branch-chip-row {
337
+ display: flex;
338
+ align-items: center;
339
+ border-radius: 7px;
340
+ }
341
+
342
+ .branch-chip-row:hover {
343
+ background: var(--bg-alt);
344
+ }
345
+
346
+ .branch-chip-row-main,
347
+ .branch-chip-row-action,
348
+ .branch-chip-new {
349
+ border: 0;
350
+ background: transparent;
351
+ color: var(--text-secondary);
352
+ font-family: inherit;
353
+ cursor: pointer;
354
+ }
355
+
356
+ .branch-chip-row-main {
357
+ min-width: 0;
358
+ flex: 1;
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 7px;
362
+ padding: 8px;
363
+ text-align: left;
364
+ }
365
+
366
+ .branch-chip-row-main:disabled {
367
+ opacity: 0.38;
368
+ cursor: not-allowed;
369
+ }
370
+
371
+ .branch-chip-row-main svg,
372
+ .branch-chip-row-action svg,
373
+ .branch-chip-new svg {
374
+ width: 14px;
375
+ height: 14px;
376
+ flex-shrink: 0;
377
+ }
378
+
379
+ .branch-chip-row-label {
380
+ overflow: hidden;
381
+ text-overflow: ellipsis;
382
+ white-space: nowrap;
383
+ }
384
+
385
+ .branch-chip-row.current .branch-chip-row-label {
386
+ color: var(--text);
387
+ font-weight: 700;
388
+ }
389
+
390
+ .branch-chip-row-main > .lucide-check {
391
+ margin-left: auto;
392
+ color: var(--accent);
393
+ }
394
+
395
+ .branch-chip-processing {
396
+ width: 7px;
397
+ height: 7px;
398
+ border-radius: 50%;
399
+ background: var(--success, #23a55a);
400
+ flex-shrink: 0;
401
+ }
402
+
403
+ .branch-chip-pending {
404
+ min-width: 17px;
405
+ padding: 1px 4px;
406
+ border-radius: 8px;
407
+ background: var(--danger, #e74c3c);
408
+ color: #fff;
409
+ font-size: 9px;
410
+ text-align: center;
411
+ }
412
+
413
+ .branch-chip-row-action {
414
+ display: flex;
415
+ padding: 7px;
416
+ opacity: 0;
417
+ }
418
+
419
+ .branch-chip-row:hover .branch-chip-row-action,
420
+ .branch-chip-row-action:focus-visible {
421
+ opacity: 0.7;
422
+ }
423
+
424
+ .branch-chip-row-action:hover {
425
+ color: var(--danger, #e74c3c);
426
+ opacity: 1;
427
+ }
428
+
429
+ .branch-chip-new {
430
+ width: 100%;
431
+ display: flex;
432
+ align-items: center;
433
+ gap: 7px;
434
+ margin-top: 5px;
435
+ padding: 9px 8px 7px;
436
+ border-top: 1px solid var(--border-subtle);
437
+ text-align: left;
438
+ }
439
+
440
+ .branch-chip-new:hover {
441
+ color: var(--text);
442
+ }
443
+
275
444
  .project-rename-input {
276
445
  width: 100%;
277
446
  font-family: "Pretendard", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
@@ -210,6 +210,12 @@
210
210
  <span class="title-bar-project-name" id="title-bar-project-name"></span>
211
211
  <i data-lucide="chevron-down" class="title-bar-chevron"></i>
212
212
  </button>
213
+ <button id="branch-chip" class="branch-chip hidden" type="button" aria-label="Switch branch" aria-expanded="false" aria-haspopup="menu">
214
+ <i data-lucide="git-branch"></i>
215
+ <span id="branch-chip-label"></span>
216
+ <i data-lucide="chevron-down" class="branch-chip-chevron"></i>
217
+ </button>
218
+ <div id="branch-chip-menu" class="branch-chip-menu hidden"></div>
213
219
  <span id="sidebar-presence" class="sidebar-presence"></span>
214
220
  <button id="sidebar-toggle-btn" class="sidebar-collapse-btn" title="Collapse sidebar"><i data-lucide="panel-left-close"></i></button>
215
221
  </div>
@@ -106,14 +106,6 @@ export function blinkIO() {
106
106
  var sessionDot = document.querySelector(".session-item.active .session-processing") ||
107
107
  document.querySelector(".mate-session-item.active .session-processing");
108
108
  if (sessionDot) sessionDot.classList.add("io");
109
- // If active project is a worktree, also blink the parent project dot
110
- var activeWt = document.querySelector("#icon-strip-projects .icon-strip-wt-item.active");
111
- var parentDot = null;
112
- if (activeWt) {
113
- var group = activeWt.closest(".icon-strip-group");
114
- if (group) parentDot = group.querySelector(".folder-header .icon-strip-status");
115
- if (parentDot) parentDot.classList.add("io");
116
- }
117
109
  // Mobile chat chip dot + mobile session dot
118
110
  var mobileChipDot = null;
119
111
  var _s = store.snap();
@@ -132,7 +124,6 @@ export function blinkIO() {
132
124
  var sd = document.querySelector(".session-item.active .session-processing.io") ||
133
125
  document.querySelector(".mate-session-item.active .session-processing.io");
134
126
  if (sd) sd.classList.remove("io");
135
- if (parentDot) parentDot.classList.remove("io");
136
127
  if (mobileChipDot) mobileChipDot.classList.remove("io");
137
128
  if (mobileSessionDot) mobileSessionDot.classList.remove("io");
138
129
  }, 80);
@@ -152,7 +143,7 @@ export function blinkSessionDot(sessionId) {
152
143
  export function updateCrossProjectBlink() {
153
144
  if (crossProjectBlinkTimer) { clearTimeout(crossProjectBlinkTimer); crossProjectBlinkTimer = null; }
154
145
  function doBlink() {
155
- var dots = document.querySelectorAll("#icon-strip-projects .icon-strip-item:not(.active) .icon-strip-status.processing, #icon-strip-projects .icon-strip-wt-item:not(.active) .icon-strip-status.processing, #icon-strip-users .icon-strip-mate:not(.active) .icon-strip-status.processing");
146
+ var dots = document.querySelectorAll("#icon-strip-projects .icon-strip-item:not(.active) .icon-strip-status.processing, #icon-strip-users .icon-strip-mate:not(.active) .icon-strip-status.processing");
156
147
  // Also blink mobile chat chip dots (same icon-strip-status class inside chips)
157
148
  var mobileDots = document.querySelectorAll(".mobile-chat-chip .icon-strip-status.processing");
158
149
  var allDots = [];
@@ -619,6 +619,7 @@ export function showHomeHub() {
619
619
  // anywhere via the persistent FAB (#clay-fab), not embedded here.
620
620
  if (store.get('dmMode')) exitDmMode();
621
621
  homeHubVisible = true;
622
+ store.set({ homeHubVisible: true });
622
623
  homeHub.classList.remove("hidden");
623
624
  // Show close button only if there's a project to return to
624
625
  if (hubCloseBtn) {
@@ -652,6 +653,7 @@ export function showHomeHub() {
652
653
  export function hideHomeHub() {
653
654
  if (!homeHubVisible) return;
654
655
  homeHubVisible = false;
656
+ store.set({ homeHubVisible: false });
655
657
  homeHub.classList.add("hidden");
656
658
  stopTipRotation();
657
659
  var mobileHome = document.getElementById("mobile-home-btn");
@@ -47,7 +47,7 @@ import { setStatus } from './app-connection.js';
47
47
  import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
48
48
  import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
49
49
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
50
- import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
50
+ import { updateProjectList, updateTitleBarProjectIdentity, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
51
51
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
52
52
  import { hideHomeHub, handleHubSchedules } from './app-home-hub.js';
53
53
  import { openDm, enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
@@ -82,8 +82,7 @@ export function processMessage(msg) {
82
82
  // Override title bar with mate name and re-apply color
83
83
  var _mdn = (store.get('dmTargetUser').displayName || "New Mate");
84
84
  if (headerTitleEl) headerTitleEl.textContent = _mdn;
85
- var _tbpn = document.getElementById("title-bar-project-name");
86
- if (_tbpn) _tbpn.textContent = _mdn;
85
+ updateTitleBarProjectIdentity(_mdn, null);
87
86
  var _mc2 = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
88
87
  var _tbc2 = document.querySelector(".title-bar-content");
89
88
  if (_tbc2) { _tbc2.style.background = _mc2; _tbc2.classList.add("mate-dm-active"); }
@@ -296,8 +295,7 @@ export function processMessage(msg) {
296
295
  if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) {
297
296
  var _mateDN = store.get('dmTargetUser').displayName || "New Mate";
298
297
  headerTitleEl.textContent = _mateDN;
299
- var tbProjectName = document.getElementById("title-bar-project-name");
300
- if (tbProjectName) tbProjectName.textContent = _mateDN;
298
+ updateTitleBarProjectIdentity(_mateDN, null);
301
299
  // Re-apply mate title bar styling (may be lost during project switch)
302
300
  var _mc = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
303
301
  var _tbc = document.querySelector(".title-bar-content");
@@ -305,8 +303,7 @@ export function processMessage(msg) {
305
303
  document.body.classList.add("mate-dm-active");
306
304
  } else {
307
305
  headerTitleEl.textContent = store.get('projectName');
308
- var tbProjectName = document.getElementById("title-bar-project-name");
309
- if (tbProjectName) tbProjectName.textContent = msg.title || store.get('projectName');
306
+ updateTitleBarProjectIdentity(msg.title || store.get('projectName'), null);
310
307
  }
311
308
  updatePageTitle();
312
309
  if (msg.version) {