clay-server 3.0.0 → 3.1.0-beta.1

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.
@@ -165,9 +165,111 @@
165
165
  display: flex;
166
166
  }
167
167
 
168
+ /* --- Delegated work-order card ------------------------------------- */
169
+ /* Self-contained: the generic bubble avatar/name/time are hidden and the
170
+ identity lives in the card's own header strip. The body stays on a
171
+ NEUTRAL background -- accent only touches the header and the top rule,
172
+ so long technical briefs read like a document, not a tinted bubble. */
173
+ .msg-user-delegated .dm-bubble-avatar,
174
+ .msg-user-delegated .dm-bubble-header {
175
+ display: none;
176
+ }
177
+
168
178
  .msg-user-delegated .bubble {
169
- background: color-mix(in srgb, var(--accent) 11%, var(--bg-elevated, var(--bg-hover)));
170
- border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
179
+ padding: 0;
180
+ overflow: hidden;
181
+ background: var(--bg);
182
+ border: 1px solid var(--border);
183
+ border-top: 2px solid var(--accent);
184
+ border-radius: 12px;
185
+ max-width: min(720px, 100%);
186
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14), 0 4px 14px rgba(0, 0, 0, 0.08);
187
+ }
188
+
189
+ .delegated-card-head {
190
+ display: flex;
191
+ align-items: center;
192
+ gap: 10px;
193
+ padding: 10px 14px;
194
+ background: linear-gradient(to bottom,
195
+ color-mix(in srgb, var(--accent) 7%, var(--bg-alt)),
196
+ var(--bg-alt));
197
+ border-bottom: 1px solid var(--border-subtle);
198
+ }
199
+
200
+ .delegated-card-avatar {
201
+ width: 24px;
202
+ height: 24px;
203
+ border-radius: 7px;
204
+ flex-shrink: 0;
205
+ }
206
+
207
+ .delegated-card-headtext {
208
+ display: grid;
209
+ gap: 1px;
210
+ min-width: 0;
211
+ flex: 1;
212
+ }
213
+
214
+ .delegated-card-eyebrow {
215
+ font-size: 9px;
216
+ font-weight: 700;
217
+ letter-spacing: 0.09em;
218
+ text-transform: uppercase;
219
+ color: var(--accent);
220
+ line-height: 1;
221
+ }
222
+
223
+ .delegated-card-from {
224
+ font-size: 12.5px;
225
+ font-weight: 650;
226
+ color: var(--text);
227
+ overflow: hidden;
228
+ text-overflow: ellipsis;
229
+ white-space: nowrap;
230
+ }
231
+
232
+ .delegated-card-time {
233
+ font-size: 10px;
234
+ color: var(--text-dimmer);
235
+ flex-shrink: 0;
236
+ align-self: flex-start;
237
+ margin-top: 2px;
238
+ }
239
+
240
+ .msg-user-delegated .delegated-brief {
241
+ padding: 13px 16px 15px;
242
+ font-size: 13px;
243
+ line-height: 1.7;
244
+ }
245
+ .msg-user-delegated .delegated-brief > :first-child { margin-top: 0; }
246
+ .msg-user-delegated .delegated-brief > :last-child { margin-bottom: 0; }
247
+ .msg-user-delegated .delegated-brief p { margin: 8px 0; }
248
+ .msg-user-delegated .delegated-brief pre {
249
+ max-width: 100%;
250
+ overflow-x: auto;
251
+ border: 1px solid var(--border-subtle);
252
+ border-radius: 8px;
253
+ }
254
+ .msg-user-delegated .delegated-brief code {
255
+ font-size: 12px;
256
+ }
257
+ .msg-user-delegated .delegated-brief h1,
258
+ .msg-user-delegated .delegated-brief h2,
259
+ .msg-user-delegated .delegated-brief h3 {
260
+ font-size: 13px;
261
+ font-weight: 700;
262
+ margin: 14px 0 4px;
263
+ }
264
+ .msg-user-delegated .delegated-brief ol,
265
+ .msg-user-delegated .delegated-brief ul {
266
+ padding-left: 20px;
267
+ margin: 6px 0;
268
+ }
269
+ .msg-user-delegated .delegated-brief li { margin: 3px 0; }
270
+ .msg-user-delegated .delegated-brief li::marker {
271
+ color: var(--accent);
272
+ font-weight: 650;
171
273
  }
172
274
 
173
275
  .msg-user-delegated .dm-bubble-avatar-other {
@@ -160,10 +160,6 @@
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
-
167
163
  .mobile-project-abbrev {
168
164
  width: 32px;
169
165
  height: 32px;
@@ -187,6 +187,33 @@ button.split-pair-role:hover { filter: brightness(1.25); }
187
187
  @keyframes pair-divider-flow-left { 0%, 100% { transform: translateX(3px); opacity: 0.65; } 50% { transform: translateX(-3px); opacity: 1; } }
188
188
  @keyframes pair-status-pulse { 50% { opacity: 0.55; } }
189
189
 
190
+ /* Worker-pane composer notice: shown while this pane's session processes a
191
+ delegated turn. Informational only -- the composer stays enabled. */
192
+ .pane-delegation-notice {
193
+ display: none;
194
+ align-items: center;
195
+ gap: 6px;
196
+ margin: 0 12px;
197
+ padding: 4px 10px;
198
+ border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
199
+ border-bottom: 0;
200
+ border-radius: 8px 8px 0 0;
201
+ background: color-mix(in srgb, var(--accent) 8%, var(--bg));
202
+ color: var(--text-secondary);
203
+ font-size: 11px;
204
+ line-height: 1.4;
205
+ }
206
+ .pane-delegation-notice.visible { display: flex; }
207
+ .pane-delegation-notice::before {
208
+ content: "";
209
+ width: 6px;
210
+ height: 6px;
211
+ border-radius: 50%;
212
+ background: var(--accent);
213
+ flex-shrink: 0;
214
+ animation: pair-status-pulse 1.5s ease-in-out infinite;
215
+ }
216
+
190
217
  /* Pair dialog rides the standard wt-modal pattern; these classes only add
191
218
  the two-column role layout on top of it. */
192
219
  .pair-modal { min-width: 480px; max-width: 560px; }
@@ -677,3 +677,13 @@
677
677
  border: 1px dashed var(--border, #444);
678
678
  }
679
679
 
680
+
681
+ /* Attention pulse: shown when the user sends a message while a permission
682
+ request is still unanswered (the turn is blocked on it). */
683
+ .permission-container.permission-attention {
684
+ animation: permission-attention-pulse 0.65s ease-in-out 3;
685
+ }
686
+ @keyframes permission-attention-pulse {
687
+ 0%, 100% { box-shadow: none; }
688
+ 50% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent); }
689
+ }
@@ -72,6 +72,15 @@
72
72
  box-shadow: 0 4px 20px rgba(var(--shadow-rgb), 0.2);
73
73
  }
74
74
 
75
+ .sticky-note.sticky-note-attention {
76
+ animation: sticky-note-attention-pulse 0.65s ease-in-out 3;
77
+ }
78
+
79
+ @keyframes sticky-note-attention-pulse {
80
+ 0%, 100% { box-shadow: 0 2px 12px rgba(var(--shadow-rgb), 0.12); }
81
+ 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 55%, transparent), 0 4px 20px rgba(var(--shadow-rgb), 0.2); }
82
+ }
83
+
75
84
  .sticky-note.dragging,
76
85
  .sticky-note.resizing {
77
86
  opacity: 0.92;
@@ -109,6 +118,15 @@
109
118
  /* On hover, header stays fully visible */
110
119
  .sticky-note:hover .sticky-note-header { opacity: 1; }
111
120
 
121
+ .sticky-note-agent-badge {
122
+ width: 16px;
123
+ height: 16px;
124
+ border-radius: 4px;
125
+ object-fit: cover;
126
+ flex: 0 0 auto;
127
+ opacity: 0.8;
128
+ }
129
+
112
130
  .sticky-note-spacer {
113
131
  flex: 1;
114
132
  min-width: 0;
@@ -272,175 +272,6 @@
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
-
444
275
  .project-rename-input {
445
276
  width: 100%;
446
277
  font-family: "Pretendard", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
@@ -210,12 +210,6 @@
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>
219
213
  <span id="sidebar-presence" class="sidebar-presence"></span>
220
214
  <button id="sidebar-toggle-btn" class="sidebar-collapse-btn" title="Collapse sidebar"><i data-lucide="panel-left-close"></i></button>
221
215
  </div>
@@ -109,6 +109,14 @@ export function blinkIO() {
109
109
  var sessionDot = document.querySelector(".session-item.active .session-processing") ||
110
110
  document.querySelector(".mate-session-item.active .session-processing");
111
111
  if (sessionDot) sessionDot.classList.add("io");
112
+ // If active project is a worktree, also blink the parent project dot
113
+ var activeWt = document.querySelector("#icon-strip-projects .icon-strip-wt-item.active");
114
+ var parentDot = null;
115
+ if (activeWt) {
116
+ var group = activeWt.closest(".icon-strip-group");
117
+ if (group) parentDot = group.querySelector(".folder-header .icon-strip-status");
118
+ if (parentDot) parentDot.classList.add("io");
119
+ }
112
120
  // Mobile chat chip dot + mobile session dot
113
121
  var mobileChipDot = null;
114
122
  var _s = store.snap();
@@ -127,6 +135,7 @@ export function blinkIO() {
127
135
  var sd = document.querySelector(".session-item.active .session-processing.io") ||
128
136
  document.querySelector(".mate-session-item.active .session-processing.io");
129
137
  if (sd) sd.classList.remove("io");
138
+ if (parentDot) parentDot.classList.remove("io");
130
139
  if (mobileChipDot) mobileChipDot.classList.remove("io");
131
140
  if (mobileSessionDot) mobileSessionDot.classList.remove("io");
132
141
  }, 80);
@@ -146,7 +155,7 @@ export function blinkSessionDot(sessionId) {
146
155
  export function updateCrossProjectBlink() {
147
156
  if (crossProjectBlinkTimer) { clearTimeout(crossProjectBlinkTimer); crossProjectBlinkTimer = null; }
148
157
  function doBlink() {
149
- 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");
158
+ 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");
150
159
  // Also blink mobile chat chip dots (same icon-strip-status class inside chips)
151
160
  var mobileDots = document.querySelectorAll(".mobile-chat-chip .icon-strip-status.processing");
152
161
  var allDots = [];
@@ -619,7 +619,6 @@ 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 });
623
622
  homeHub.classList.remove("hidden");
624
623
  // Show close button only if there's a project to return to
625
624
  if (hubCloseBtn) {
@@ -653,7 +652,6 @@ export function showHomeHub() {
653
652
  export function hideHomeHub() {
654
653
  if (!homeHubVisible) return;
655
654
  homeHubVisible = false;
656
- store.set({ homeHubVisible: false });
657
655
  homeHub.classList.add("hidden");
658
656
  stopTipRotation();
659
657
  var mobileHome = document.getElementById("mobile-home-btn");
@@ -20,7 +20,7 @@ import { renderMemoryList } from './mate-memory.js';
20
20
  import { handlePaletteSessionSwitch, setPaletteVersion } from './command-palette.js';
21
21
  import { handleFindInSessionResults } from './session-search.js';
22
22
  import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
23
- import { showPairDialog, handlePairCreated, handleSplitDelegation } from './split-pair-ui.js';
23
+ import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
24
24
  import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled } from './input.js';
25
25
  import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved, renderUserDialogRequest, markUserDialogResolved, updateThinkingTokens } from './tools.js';
26
26
  import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
@@ -33,7 +33,7 @@ import { handleTuiTranscriptState } from './tui-grab.js';
33
33
  import { tuiModalHandleTermOutput, tuiModalHandleTermResized, tuiModalHandleTermExited, tuiModalHandleTermClosed, openTuiModal } from './tui-attention.js';
34
34
  import { updateTerminalList, handleContextSourcesState, updateEmailAccountList, updateEmailUnreadCounts, handleEmailTestResult, handleEmailAddResult, handleEmailRemoveResult, handleEmailDefaults } from './context-sources.js';
35
35
  import { refreshEmailSettings } from './user-settings.js';
36
- import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted } from './sticky-notes.js';
36
+ import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, handleNoteWritten } from './sticky-notes.js';
37
37
  import { handleSkillInstalled, handleSkillUninstalled } from './skills.js';
38
38
  import { showRewindModal, onRewindComplete, setRewindMode, onRewindError, clearPendingRewindUuid, addRewindButton } from './rewind.js';
39
39
  import { checkAdminAccess } from './admin.js';
@@ -50,7 +50,7 @@ import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWh
50
50
  import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
51
51
  import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
52
52
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
53
- import { updateProjectList, updateTitleBarProjectIdentity, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
53
+ import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
54
54
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
55
55
  import { hideHomeHub, handleHubSchedules } from './app-home-hub.js';
56
56
  import { openDm, enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
@@ -85,7 +85,8 @@ export function processMessage(msg) {
85
85
  // Override title bar with mate name and re-apply color
86
86
  var _mdn = (store.get('dmTargetUser').displayName || "New Mate");
87
87
  if (headerTitleEl) headerTitleEl.textContent = _mdn;
88
- updateTitleBarProjectIdentity(_mdn, null);
88
+ var _tbpn = document.getElementById("title-bar-project-name");
89
+ if (_tbpn) _tbpn.textContent = _mdn;
89
90
  var _mc2 = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
90
91
  var _tbc2 = document.querySelector(".title-bar-content");
91
92
  if (_tbc2) { _tbc2.style.background = _mc2; _tbc2.classList.add("mate-dm-active"); }
@@ -298,7 +299,8 @@ export function processMessage(msg) {
298
299
  if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) {
299
300
  var _mateDN = store.get('dmTargetUser').displayName || "New Mate";
300
301
  headerTitleEl.textContent = _mateDN;
301
- updateTitleBarProjectIdentity(_mateDN, null);
302
+ var tbProjectName = document.getElementById("title-bar-project-name");
303
+ if (tbProjectName) tbProjectName.textContent = _mateDN;
302
304
  // Re-apply mate title bar styling (may be lost during project switch)
303
305
  var _mc = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
304
306
  var _tbc = document.querySelector(".title-bar-content");
@@ -306,7 +308,8 @@ export function processMessage(msg) {
306
308
  document.body.classList.add("mate-dm-active");
307
309
  } else {
308
310
  headerTitleEl.textContent = store.get('projectName');
309
- updateTitleBarProjectIdentity(msg.title || store.get('projectName'), null);
311
+ var tbProjectName = document.getElementById("title-bar-project-name");
312
+ if (tbProjectName) tbProjectName.textContent = msg.title || store.get('projectName');
310
313
  }
311
314
  updatePageTitle();
312
315
  if (msg.version) {
@@ -788,6 +791,9 @@ export function processMessage(msg) {
788
791
  } else {
789
792
  addUserMessage(msg.text, msg.images || null, msg.pastes || null, msg.from, msg.fromName, msg.delegated ? msg : null);
790
793
  }
794
+ // Worker pane: a delegated instruction starts (or replays into) a
795
+ // driven turn; surface the composer notice. Cleared on "done".
796
+ if (msg.delegated) showWorkerDelegationNotice(msg.delegatedByTitle || null);
791
797
  break;
792
798
 
793
799
  case "plan_content":
@@ -1108,6 +1114,7 @@ export function processMessage(msg) {
1108
1114
  removeMatePreThinking();
1109
1115
  setActivity(null);
1110
1116
  stopThinking();
1117
+ hideWorkerDelegationNotice();
1111
1118
  markAllToolsDone();
1112
1119
  closeToolGroup();
1113
1120
  finalizeAssistantBlock();
@@ -1441,6 +1448,11 @@ export function processMessage(msg) {
1441
1448
  handleNoteUpdated(msg);
1442
1449
  break;
1443
1450
 
1451
+ case "note_written":
1452
+ showToast((msg.byTitle || "Agent") + ' left a note: "' + (msg.preview || "") + '"');
1453
+ handleNoteWritten(msg);
1454
+ break;
1455
+
1444
1456
  case "note_deleted":
1445
1457
  handleNoteDeleted(msg);
1446
1458
  break;
@@ -32,7 +32,6 @@ import { resetRateLimitState } from './app-rate-limit.js';
32
32
  import { closeSessionInfoPopover } from './app-header.js';
33
33
  import { resetDebateState } from './debate.js';
34
34
  import { removeDebateBottomBar } from './app-debate-ui.js';
35
- import { familyOf as deriveFamily, displayProject } from './worktree-family.js';
36
35
 
37
36
  // --- Module-owned state ---
38
37
  var cachedProjects = [];
@@ -202,32 +201,6 @@ export function setCachedProjectCount(v) { cachedProjectCount = v; }
202
201
  export function getCachedRemovedProjects() { return cachedRemovedProjects; }
203
202
  export function setCachedRemovedProjects(v) { cachedRemovedProjects = v; }
204
203
 
205
- export function familyOf(slug) { return deriveFamily(cachedProjects, slug); }
206
- export function currentFamily() { return familyOf(store.get('currentSlug')); }
207
-
208
- export function updateTitleBarProjectIdentity(fallbackName, fallbackIcon) {
209
- if (store.get('dmMode') || store.get('mateProjectSlug')) {
210
- var dmNameEl = document.getElementById("title-bar-project-name");
211
- if (dmNameEl && fallbackName) dmNameEl.textContent = fallbackName;
212
- return;
213
- }
214
- var identity = displayProject(cachedProjects, store.get('currentSlug'));
215
- var name = identity ? (identity.title || identity.project || identity.slug) : fallbackName;
216
- var icon = identity ? (identity.icon || null) : (fallbackIcon || null);
217
- var nameEl = document.getElementById("title-bar-project-name");
218
- if (nameEl && name) nameEl.textContent = name;
219
- var iconEl = document.getElementById("title-bar-project-icon");
220
- if (!iconEl) return;
221
- if (icon) {
222
- iconEl.textContent = icon;
223
- parseEmojis(iconEl);
224
- iconEl.classList.add("has-icon");
225
- } else {
226
- iconEl.textContent = "";
227
- iconEl.classList.remove("has-icon");
228
- }
229
- }
230
-
231
204
  // --- Functions ---
232
205
 
233
206
  export function updateProjectList(msg) {
@@ -242,7 +215,6 @@ export function updateProjectList(msg) {
242
215
  _lastProjectsJson = projectsJson;
243
216
  }
244
217
  cachedProjects = msg.projects;
245
- store.set({ projects: cachedProjects });
246
218
  }
247
219
  if (msg.removedProjects) cachedRemovedProjects = msg.removedProjects;
248
220
  else if (msg.removedProjects === undefined) { /* keep cached */ }
@@ -354,12 +326,31 @@ export function renderProjectList() {
354
326
  });
355
327
  var st = store.snap();
356
328
  var iconStripActiveSlug = (st.mateProjectSlug && st.savedMainSlug) ? st.savedMainSlug : st.currentSlug;
357
- var activeFamily = deriveFamily(cachedProjects, iconStripActiveSlug);
358
- if (activeFamily.parent) iconStripActiveSlug = activeFamily.parent.slug;
359
329
  renderIconStrip(iconStripProjects, iconStripActiveSlug);
360
330
  // Update title bar project name and icon if it changed
361
331
  if (!st.mateProjectSlug) {
362
- updateTitleBarProjectIdentity(store.get('projectName'), null);
332
+ for (var pi = 0; pi < cachedProjects.length; pi++) {
333
+ if (cachedProjects[pi].slug === st.currentSlug) {
334
+ var updatedName = cachedProjects[pi].title || cachedProjects[pi].project;
335
+ var tbName = document.getElementById("title-bar-project-name");
336
+ if (tbName && updatedName) tbName.textContent = updatedName;
337
+ var tbIcon = document.getElementById("title-bar-project-icon");
338
+ if (tbIcon) {
339
+ var pIcon = cachedProjects[pi].icon || null;
340
+ if (pIcon) {
341
+ tbIcon.textContent = pIcon;
342
+ parseEmojis(tbIcon);
343
+ tbIcon.classList.add("has-icon");
344
+ try { localStorage.setItem("clay-project-icon-" + (st.currentSlug || "default"), pIcon); } catch (e) {}
345
+ } else {
346
+ tbIcon.textContent = "";
347
+ tbIcon.classList.remove("has-icon");
348
+ try { localStorage.removeItem("clay-project-icon-" + (st.currentSlug || "default")); } catch (e) {}
349
+ }
350
+ }
351
+ break;
352
+ }
353
+ }
363
354
  }
364
355
  // Re-apply current socket status to the active icon's dot
365
356
  var dot = getStatusDot();
@@ -566,13 +557,9 @@ function showRemoveProjectTaskDialog(slug, name, taskCount) {
566
557
  export function handleRemoveProjectResult(msg) {
567
558
  if (msg.ok) {
568
559
  var currentSlug = store.get('currentSlug');
569
- var removedProj = null;
570
- for (var ri = 0; ri < cachedProjects.length; ri++) {
571
- if (cachedProjects[ri].slug === msg.slug) { removedProj = cachedProjects[ri]; break; }
572
- }
573
- var isWorktree = removedProj ? !!removedProj.isWorktree : msg.slug.indexOf("--") !== -1;
574
560
  if (msg.slug === currentSlug) {
575
- var parentSlug = isWorktree ? ((removedProj && removedProj.parentSlug) || msg.slug.split("--")[0]) : null;
561
+ var isWorktree = msg.slug.indexOf("--") !== -1;
562
+ var parentSlug = isWorktree ? msg.slug.split("--")[0] : null;
576
563
 
577
564
  showToast(isWorktree ? "Worktree removed" : "Project removed", "success");
578
565
 
@@ -583,6 +570,10 @@ export function handleRemoveProjectResult(msg) {
583
570
  store.set({ connected: false });
584
571
  document.getElementById("connect-overlay").classList.add("hidden");
585
572
  if (!isWorktree) {
573
+ var removedProj = null;
574
+ for (var ri = 0; ri < cachedProjects.length; ri++) {
575
+ if (cachedProjects[ri].slug === msg.slug) { removedProj = cachedProjects[ri]; break; }
576
+ }
586
577
  if (removedProj) {
587
578
  cachedRemovedProjects.push({
588
579
  path: removedProj.path || "",
@@ -594,7 +585,7 @@ export function handleRemoveProjectResult(msg) {
594
585
  }
595
586
  cachedProjects = cachedProjects.filter(function (p) { return p.slug !== msg.slug; });
596
587
  cachedProjectCount = cachedProjects.length;
597
- store.set({ currentSlug: null, projects: cachedProjects });
588
+ store.set({ currentSlug: null });
598
589
  renderProjectList();
599
590
  resetClientState();
600
591
 
@@ -604,11 +595,7 @@ export function handleRemoveProjectResult(msg) {
604
595
  showHomeHub();
605
596
  }
606
597
  } else {
607
- showToast(isWorktree ? "Worktree removed" : "Project removed", "success");
608
- cachedProjects = cachedProjects.filter(function (p) { return p.slug !== msg.slug; });
609
- cachedProjectCount = cachedProjects.length;
610
- store.set({ projects: cachedProjects });
611
- renderProjectList();
598
+ showToast(msg.slug.indexOf("--") !== -1 ? "Worktree removed" : "Project removed", "success");
612
599
  }
613
600
  } else {
614
601
  showToast(msg.error || "Failed to remove project", "error");