clay-server 2.32.0 → 2.32.1-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.
@@ -41,6 +41,38 @@
41
41
  width: 340px;
42
42
  }
43
43
 
44
+ /* "Clear all" pill — appears at the top of the stack when 2+ banners
45
+ are showing. Small, muted, aligned to the right so it sits above the
46
+ banners' corner without stealing attention. */
47
+ .notif-banner-clear-all {
48
+ pointer-events: auto;
49
+ align-self: flex-end;
50
+ display: inline-flex;
51
+ align-items: center;
52
+ gap: 4px;
53
+ padding: 3px 10px;
54
+ background: color-mix(in srgb, var(--bg-alt, #1a1a2e) 75%, transparent);
55
+ backdrop-filter: blur(20px);
56
+ -webkit-backdrop-filter: blur(20px);
57
+ border: 1px solid var(--border, #333);
58
+ border-radius: 999px;
59
+ font-family: inherit;
60
+ font-size: 11px;
61
+ font-weight: 500;
62
+ color: var(--text-muted);
63
+ cursor: pointer;
64
+ box-shadow: 0 4px 20px rgba(var(--shadow-rgb, 0,0,0), 0.2);
65
+ transition: color 0.15s, background 0.15s;
66
+ }
67
+ .notif-banner-clear-all:hover {
68
+ color: var(--text);
69
+ background: color-mix(in srgb, var(--bg-alt, #1a1a2e) 90%, transparent);
70
+ }
71
+ .notif-banner-clear-all .lucide {
72
+ width: 12px;
73
+ height: 12px;
74
+ }
75
+
44
76
  /* Individual banner (frosted glass) */
45
77
  .notif-banner {
46
78
  display: flex;
@@ -98,30 +98,13 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
98
98
  .top-bar-install-btn.hidden { display: none; }
99
99
  .pwa-standalone .top-bar-install-btn { display: none !important; }
100
100
 
101
- /* Share button desktop only, same style as install pill */
102
- .top-bar-share-btn {
103
- display: inline-flex;
104
- align-items: center;
105
- gap: 4px;
106
- background: color-mix(in srgb, var(--accent) 12%, transparent);
107
- color: var(--accent);
108
- border: none;
109
- border-radius: 10px;
110
- padding: 2px 10px;
111
- font-family: inherit;
112
- font-size: 11px;
113
- font-weight: 600;
114
- cursor: pointer;
115
- white-space: nowrap;
116
- line-height: 1;
117
- transition: background 0.15s;
118
- }
119
- .top-bar-share-btn .lucide { width: 12px; height: 12px; }
120
- .top-bar-share-btn:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
101
+ /* Share button sits in .top-bar-actions with the bell/settings icons
102
+ and inherits their shared icon-button styling. Hide on mobile and
103
+ inside PWA standalone (matches previous pill behavior). */
121
104
  @media (max-width: 768px) {
122
- .top-bar-share-btn { display: none; }
105
+ #share-pill { display: none; }
123
106
  }
124
- .pwa-standalone .top-bar-share-btn { display: none !important; }
107
+ .pwa-standalone #share-pill { display: none !important; }
125
108
 
126
109
  /* Extension pill button — same style as share/install pills */
127
110
  .ext-pill-wrap {
@@ -18,6 +18,21 @@
18
18
  overflow: hidden;
19
19
  }
20
20
 
21
+ /* Sidebar items aren't meant to be text-selected — the sidebar is for
22
+ navigation and drag-reorder. Disabling selection avoids accidental
23
+ highlights during clicks and drags. Inputs and contenteditables
24
+ inside the sidebar re-enable selection so users can still edit. */
25
+ #sidebar-column {
26
+ -webkit-user-select: none;
27
+ user-select: none;
28
+ }
29
+ #sidebar-column input,
30
+ #sidebar-column textarea,
31
+ #sidebar-column [contenteditable="true"] {
32
+ -webkit-user-select: text;
33
+ user-select: text;
34
+ }
35
+
21
36
  #layout.sidebar-collapsed #sidebar-column {
22
37
  width: 0 !important;
23
38
  min-width: 0 !important;
@@ -273,52 +288,298 @@
273
288
  border-bottom: 1px solid var(--border-subtle);
274
289
  padding-bottom: 4px;
275
290
  }
276
- #sidebar-tools-toggle {
291
+
292
+ /* Section header: "Tools" label on the left, a dim keyboard-hint next
293
+ to it explaining the Cmd+O shortcut, and a small pencil icon edit
294
+ button right-aligned. Three elements but each does just one thing,
295
+ so nothing reads as pushy. */
296
+ .sidebar-tools-header {
277
297
  display: flex;
278
298
  align-items: center;
279
- justify-content: space-between;
280
- width: 100%;
281
- margin: 0;
282
- padding: 8px 8px 4px 0;
283
- box-sizing: border-box;
299
+ gap: 8px;
300
+ padding: 4px 8px 2px 12px;
301
+ }
302
+ .sidebar-tools-header .sidebar-section-label {
303
+ padding: 0;
304
+ }
305
+
306
+ /* Small shortcut-pill for the Cmd/Ctrl+O hotkey overlay. Mirrors the
307
+ icon-strip hotkey hints so keyboard shortcuts look the same across
308
+ the sidebar surface. */
309
+ .sidebar-tools-hint {
310
+ display: inline-flex;
311
+ align-items: center;
312
+ justify-content: center;
313
+ min-width: 28px;
314
+ padding: 2px 4px;
315
+ border: 1px solid var(--border-subtle);
316
+ background: var(--bg-alt);
317
+ color: var(--text-dimmer);
318
+ font-size: 10px;
319
+ font-weight: 600;
320
+ font-family: inherit;
321
+ border-radius: 6px;
322
+ cursor: pointer;
323
+ opacity: 0.7;
324
+ transition: opacity 0.15s, color 0.15s, background 0.15s;
325
+ }
326
+ .sidebar-tools-hint:hover {
327
+ opacity: 1;
328
+ color: var(--text-muted);
329
+ background: var(--bg);
330
+ }
331
+
332
+ /* Pencil-icon edit button, far-right of the header. Dim by default so
333
+ it doesn't pull attention; on hover it brightens to signal that
334
+ it's a real affordance. */
335
+ .tool-palette-edit-btn {
336
+ margin-left: auto;
337
+ display: inline-flex;
338
+ align-items: center;
339
+ justify-content: center;
340
+ width: 22px;
341
+ height: 22px;
284
342
  background: none;
285
343
  border: none;
286
344
  cursor: pointer;
287
- font-size: 11px;
345
+ color: var(--text-dimmer);
346
+ opacity: 0.45;
347
+ border-radius: 6px;
348
+ transition: background 0.15s, color 0.15s, opacity 0.15s;
349
+ }
350
+ .tool-palette-edit-btn .lucide,
351
+ .tool-palette-edit-btn svg {
352
+ width: 13px;
353
+ height: 13px;
354
+ }
355
+ .tool-palette-edit-btn:hover {
356
+ background: var(--sidebar-hover);
357
+ color: var(--text-muted);
358
+ opacity: 1;
359
+ }
360
+ .tool-palette-edit-btn.active {
361
+ background: var(--accent);
362
+ color: #fff;
363
+ opacity: 1;
364
+ }
365
+ .tool-palette-edit-btn.active:hover {
366
+ background: var(--accent);
367
+ color: #fff;
368
+ opacity: 1;
369
+ filter: brightness(1.1);
370
+ }
371
+
372
+ /* Hidden-tools section appears only in edit mode when items are hidden. */
373
+ .tool-palette-hidden-section {
374
+ padding: 4px 8px 8px;
375
+ border-top: 1px dashed var(--border-subtle);
376
+ margin-top: 4px;
377
+ }
378
+ .tool-palette-hidden-section.hidden {
379
+ display: none;
380
+ }
381
+ .tool-palette-hidden-label {
382
+ font-size: 10px;
288
383
  font-weight: 600;
289
384
  color: var(--text-dimmer);
290
385
  text-transform: uppercase;
291
386
  letter-spacing: 0.5px;
292
- font-family: inherit;
387
+ padding: 2px 4px 6px;
293
388
  }
294
- #sidebar-tools-toggle:hover {
295
- color: var(--text-muted);
389
+ .tool-palette-hidden-grid {
390
+ display: grid;
391
+ grid-template-columns: repeat(4, 1fr);
392
+ gap: 4px;
296
393
  }
297
- .sidebar-tools-chevron {
298
- width: 14px;
299
- height: 14px;
300
- transition: transform 0.15s;
394
+
395
+ /* --- Shared tile style (applies in both active and hidden grids) --- */
396
+ .palette-tile {
397
+ position: relative;
398
+ display: flex;
399
+ flex-direction: column;
400
+ align-items: center;
401
+ justify-content: center;
402
+ gap: 4px;
403
+ min-height: 58px;
404
+ padding: 6px 2px;
405
+ border-radius: 10px;
406
+ border: none;
407
+ background: transparent;
408
+ color: var(--text-muted);
409
+ font-family: inherit;
410
+ font-size: 10px;
411
+ font-weight: 500;
412
+ cursor: pointer;
413
+ transition: background 0.15s, color 0.15s;
301
414
  }
302
- #sidebar-tools #session-actions {
303
- max-height: 300px;
415
+ .palette-tile .lucide { width: 16px; height: 16px; flex-shrink: 0; }
416
+ .palette-tile:hover { background: var(--sidebar-hover); color: var(--text-secondary); }
417
+ .palette-tile.active { background: var(--sidebar-hover); color: var(--text); }
418
+ .palette-tile .tool-btn-label {
419
+ font-size: 10px;
420
+ font-weight: 500;
421
+ line-height: 1.15;
422
+ text-align: center;
423
+ max-width: 100%;
304
424
  overflow: hidden;
305
- transition: max-height 0.2s ease, opacity 0.2s ease;
425
+ text-overflow: ellipsis;
426
+ display: -webkit-box;
427
+ -webkit-line-clamp: 2;
428
+ -webkit-box-orient: vertical;
429
+ word-break: break-word;
430
+ }
431
+
432
+ /* --- Edit mode affordances (shared across session + mate palettes) --- */
433
+
434
+ /* × remove badge on each tile. Hidden in normal mode; shown in edit. */
435
+ .tool-palette-remove {
436
+ position: absolute;
437
+ top: -4px;
438
+ right: -4px;
439
+ width: 16px;
440
+ height: 16px;
441
+ border-radius: 50%;
442
+ background: var(--accent);
443
+ color: #fff;
444
+ font-size: 13px;
445
+ font-weight: 700;
446
+ line-height: 1;
447
+ display: flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ cursor: pointer;
451
+ opacity: 0;
452
+ transform: scale(0.6);
453
+ pointer-events: none;
454
+ transition: opacity 0.15s, transform 0.15s;
455
+ z-index: 2;
456
+ user-select: none;
457
+ }
458
+ .edit-mode .tool-palette-remove {
306
459
  opacity: 1;
460
+ transform: scale(1);
461
+ pointer-events: auto;
307
462
  }
308
- #sidebar-tools.collapsed .sidebar-tools-chevron {
309
- transform: rotate(-90deg);
463
+ .edit-mode .tool-palette-remove:hover {
464
+ filter: brightness(1.15);
310
465
  }
311
- #sidebar-tools.collapsed #session-actions {
312
- max-height: 0;
313
- opacity: 0;
314
- padding-bottom: 0;
466
+
467
+ /* Drag-reorder feedback. Tiles are draggable at all times (macOS dock
468
+ pattern) so users can rearrange without entering a separate mode.
469
+ -webkit-user-drag is explicit for Safari, which otherwise sometimes
470
+ ignores draggable when user-select is none on an ancestor. Edit mode
471
+ adds the outline so the tiles read as mutable during an edit. */
472
+ #session-actions [data-tool-id],
473
+ #mate-sidebar-tools [data-tool-id] {
474
+ -webkit-user-drag: element;
475
+ }
476
+ .edit-mode [data-tool-id] {
477
+ box-shadow: inset 0 0 0 1px var(--border-subtle);
478
+ animation: tile-wiggle 0.22s ease-in-out infinite;
479
+ transform-origin: 50% 50%;
480
+ }
481
+ /* Desync the wiggle across tiles so they don't swing in lockstep. */
482
+ .edit-mode [data-tool-id]:nth-child(2n) {
483
+ animation-delay: -0.11s;
484
+ }
485
+ .edit-mode [data-tool-id]:nth-child(3n) {
486
+ animation-duration: 0.26s;
315
487
  }
316
- .sidebar-tools-divider {
317
- height: 1px;
318
- background: var(--border-subtle);
319
- margin: 4px 12px;
488
+ @keyframes tile-wiggle {
489
+ 0% { transform: rotate(-1.2deg); }
490
+ 50% { transform: rotate(1.2deg); }
491
+ 100% { transform: rotate(-1.2deg); }
492
+ }
493
+ /* The dragged tile shouldn't wiggle while under the cursor. */
494
+ [data-tool-id].dragging {
495
+ opacity: 0.4;
496
+ cursor: grabbing;
497
+ animation: none;
498
+ }
499
+ /* Children shouldn't swallow drag events — the browser starts drag on
500
+ the element under the pointer, and we want that to always be the
501
+ draggable button, not the inner icon/label/badge. The × affordance
502
+ stays interactive (it needs clicks in edit mode). */
503
+ [data-tool-id] > i,
504
+ [data-tool-id] > svg,
505
+ [data-tool-id] > .tool-btn-label,
506
+ [data-tool-id] > .sidebar-badge {
507
+ pointer-events: none;
320
508
  }
321
509
 
510
+ /* Hidden-section tiles are dimmed to signal "not active" and get a small
511
+ + glyph on hover to suggest they can be restored. */
512
+ .tool-palette-hidden-grid [data-tool-id] {
513
+ opacity: 0.55;
514
+ cursor: pointer;
515
+ transition: opacity 0.15s, background 0.15s, color 0.15s;
516
+ }
517
+ .tool-palette-hidden-grid [data-tool-id]:hover {
518
+ opacity: 1;
519
+ }
520
+ .tool-palette-hidden-grid .tool-palette-remove {
521
+ display: none;
522
+ }
523
+ .tool-palette-hidden-grid .sidebar-badge {
524
+ display: none;
525
+ }
526
+
527
+ /* Hotkey badge shown on each active palette tile while Cmd/Ctrl+O pick
528
+ mode is active. Top-left so it doesn't collide with the × (top-right)
529
+ or count badges. High-contrast accent so the keystroke is obvious. */
530
+ .tool-palette-hotkey-badge {
531
+ position: absolute;
532
+ top: -4px;
533
+ left: -4px;
534
+ min-width: 16px;
535
+ height: 16px;
536
+ padding: 0 4px;
537
+ border-radius: 4px;
538
+ background: var(--accent);
539
+ color: #fff;
540
+ font-size: 10px;
541
+ font-weight: 700;
542
+ line-height: 16px;
543
+ text-align: center;
544
+ pointer-events: none;
545
+ z-index: 3;
546
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
547
+ animation: tool-palette-hotkey-in 0.12s ease-out;
548
+ }
549
+ @keyframes tool-palette-hotkey-in {
550
+ from { opacity: 0; transform: scale(0.7); }
551
+ to { opacity: 1; transform: scale(1); }
552
+ }
553
+
554
+ /* Right-click context menu on palette tiles. Mirrors .session-ctx-menu. */
555
+ .tool-palette-ctx-menu {
556
+ position: fixed;
557
+ background: var(--sidebar-bg);
558
+ border: 1px solid var(--border);
559
+ border-radius: 10px;
560
+ padding: 4px 0;
561
+ min-width: 180px;
562
+ box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.4);
563
+ z-index: 9999;
564
+ font-family: inherit;
565
+ }
566
+ .tool-palette-ctx-item {
567
+ display: flex;
568
+ align-items: center;
569
+ width: 100%;
570
+ padding: 8px 12px;
571
+ font-size: 13px;
572
+ color: var(--text-secondary);
573
+ background: none;
574
+ border: none;
575
+ font-family: inherit;
576
+ cursor: pointer;
577
+ text-align: left;
578
+ transition: background 0.15s;
579
+ }
580
+ .tool-palette-ctx-item:hover {
581
+ background: rgba(var(--overlay-rgb), 0.05);
582
+ }
322
583
  /* --- Sessions header (pinned above scroll) --- */
323
584
  #sidebar-sessions-header {
324
585
  flex-shrink: 0;
@@ -70,7 +70,6 @@
70
70
  </div>
71
71
  </div>
72
72
  </div>
73
- <button id="share-pill" class="top-bar-share-btn" title="Share"><i data-lucide="qr-code"></i> Share</button>
74
73
  <div id="update-pill-wrap" class="top-bar-update hidden">
75
74
  <button id="update-pill" class="top-bar-update-btn"><i data-lucide="arrow-up-circle"></i> <span id="update-version"></span> is available. Update now</button>
76
75
  <div id="update-popover" class="top-bar-popover">
@@ -84,6 +83,7 @@
84
83
  <!-- Pill badges -->
85
84
  <div id="skip-perms-pill" class="top-bar-pill pill-error hidden"><i data-lucide="shield-off"></i> <span>Skip perms</span></div>
86
85
  <div id="client-count" class="top-bar-pill pill-accent hidden"><i data-lucide="users"></i> <span id="client-count-text"></span></div>
86
+ <button id="share-pill" title="Share"><i data-lucide="share-2"></i></button>
87
87
  <button id="notif-center-btn" title="Notifications"><i data-lucide="bell"></i><span id="notif-center-badge" class="notif-badge hidden">0</span></button>
88
88
  <button id="server-settings-btn" title="Server settings"><i data-lucide="settings"></i></button>
89
89
  </div>
@@ -99,12 +99,16 @@
99
99
  <img class="icon-strip-logo" src="icon-banded-76.png" width="38" height="38" alt="Clay">
100
100
  </div>
101
101
  <div class="icon-strip-separator"></div>
102
+ <button type="button" class="icon-strip-hint" id="icon-strip-hint-project" data-switcher-mode="project" aria-label="Switch project"></button>
102
103
  <div class="icon-strip-projects" id="icon-strip-projects">
103
104
  <div class="skeleton-icon-strip-item"></div>
104
105
  <div class="skeleton-icon-strip-item"></div>
105
106
  </div>
106
107
  <button class="icon-strip-add" id="icon-strip-add" title="Add project"><i data-lucide="plus"></i></button>
107
- <div class="icon-strip-users hidden" id="icon-strip-users"></div>
108
+ <div class="icon-strip-mate-section" id="icon-strip-mate-section">
109
+ <button type="button" class="icon-strip-hint hidden" id="icon-strip-hint-mate" data-switcher-mode="mate" aria-label="Switch mate"></button>
110
+ <div class="icon-strip-users hidden" id="icon-strip-users"></div>
111
+ </div>
108
112
  <div class="icon-strip-me" id="icon-strip-me"></div>
109
113
  </div>
110
114
 
@@ -183,21 +187,13 @@
183
187
  </div>
184
188
  <div id="sidebar">
185
189
  <div id="sidebar-tools">
186
- <button id="sidebar-tools-toggle" type="button">
190
+ <div class="sidebar-tools-header">
187
191
  <span class="sidebar-section-label">Tools</span>
188
- <i data-lucide="chevron-down" class="sidebar-tools-chevron"></i>
189
- </button>
190
- <div id="session-actions">
191
- <button id="file-browser-btn"><i data-lucide="folder-tree"></i> <span>File browser</span></button>
192
- <button id="terminal-sidebar-btn"><i data-lucide="square-terminal"></i> <span>Terminal</span><span id="terminal-sidebar-count" class="sidebar-badge hidden"></span></button>
193
- <button id="sticky-notes-sidebar-btn"><i data-lucide="sticky-note"></i> <span>Sticky Notes</span><span id="sticky-notes-sidebar-count" class="sidebar-badge hidden"></span></button>
194
- <div class="sidebar-tools-divider"></div>
195
- <button id="email-sidebar-btn"><i data-lucide="mail"></i> <span>Email</span></button>
196
- <button id="mcp-btn"><i data-lucide="cable"></i> <span>MCP Servers</span><span id="mcp-sidebar-count" class="sidebar-badge hidden"></span></button>
197
- <button id="skills-btn"><i data-lucide="puzzle"></i> <span>Skills</span></button>
198
- <div class="sidebar-tools-divider"></div>
199
- <button id="scheduler-btn"><i data-lucide="calendar-clock"></i> <span>Scheduled Tasks</span></button>
192
+ <span class="sidebar-tools-hint" data-palette="session" title="Show keyboard hotkeys"></span>
193
+ <button type="button" class="tool-palette-edit-btn" data-palette="session" aria-label="Edit tool palette" title="Edit palette"><i data-lucide="pencil"></i></button>
200
194
  </div>
195
+ <div id="session-actions"></div>
196
+ <div id="session-actions-hidden" class="tool-palette-hidden-section hidden"></div>
201
197
  </div>
202
198
  <div id="sidebar-sessions-header">
203
199
  <div id="sessions-header-content">
@@ -242,6 +238,11 @@
242
238
  <input id="fb-search-input" type="text" placeholder="Search files..." autocomplete="off" spellcheck="false" />
243
239
  </div>
244
240
  <div id="file-tree"></div>
241
+ <div class="fb-kb-hint">
242
+ <kbd>&#8593;&#8595;</kbd><span>nav</span>
243
+ <kbd>&#8592;&#8594;</kbd><span>fold</span>
244
+ <kbd>&#8629;</kbd><span>open</span>
245
+ </div>
245
246
  </div>
246
247
  </div>
247
248
  </div>
@@ -257,14 +258,14 @@
257
258
  <div id="mate-sidebar-seed-tooltip" class="mate-seed-tooltip hidden"></div>
258
259
  <button id="mate-sidebar-toggle-btn" class="sidebar-collapse-btn" title="Collapse sidebar"><i data-lucide="panel-left-close"></i></button>
259
260
  </div>
260
- <div id="mate-sidebar-tools">
261
- <button id="mate-memory-btn"><i data-lucide="brain"></i> <span>Memory</span><span id="mate-memory-count" class="sidebar-badge hidden"></span></button>
262
- <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>
263
- <button id="mate-sticky-notes-btn"><i data-lucide="sticky-note"></i> <span>Sticky Notes</span></button>
264
- <button id="mate-email-btn"><i data-lucide="mail"></i> <span>Email</span></button>
265
- <button id="mate-mcp-btn"><i data-lucide="cable"></i> <span>MCP Servers</span><span id="mate-mcp-sidebar-count" class="sidebar-badge hidden"></span></button>
266
- <button id="mate-skills-btn"><i data-lucide="puzzle"></i> <span>Skills</span></button>
267
- <button id="mate-scheduler-btn"><i data-lucide="calendar-clock"></i> <span>Scheduled Tasks</span></button>
261
+ <div id="mate-sidebar-tools-wrap">
262
+ <div class="sidebar-tools-header">
263
+ <span class="sidebar-section-label">Tools</span>
264
+ <span class="sidebar-tools-hint" data-palette="mate" title="Show keyboard hotkeys"></span>
265
+ <button type="button" class="tool-palette-edit-btn" data-palette="mate" aria-label="Edit tool palette" title="Edit palette"><i data-lucide="pencil"></i></button>
266
+ </div>
267
+ <div id="mate-sidebar-tools"></div>
268
+ <div id="mate-sidebar-tools-hidden" class="tool-palette-hidden-section hidden"></div>
268
269
  </div>
269
270
  <div id="mate-sidebar-conversations">
270
271
  <div class="mate-sidebar-sessions-header">
@@ -126,6 +126,7 @@ function showBanner(notif, autoDismissMs) {
126
126
 
127
127
  bannerContainer.appendChild(banner);
128
128
  refreshIcons();
129
+ ensureClearAllButton();
129
130
 
130
131
  requestAnimationFrame(function () {
131
132
  banner.classList.add("show");
@@ -204,11 +205,62 @@ function removeBanner(banner) {
204
205
  if (!banner || !banner.parentNode) return;
205
206
  banner.classList.remove("show");
206
207
  banner.classList.add("hide");
208
+ // Update the clear-all button right away so it disappears before the
209
+ // last banner finishes animating out, and again after the node is
210
+ // actually removed in case the count check depends on DOM presence.
211
+ ensureClearAllButton();
207
212
  setTimeout(function () {
208
213
  if (banner.parentNode) banner.parentNode.removeChild(banner);
214
+ ensureClearAllButton();
209
215
  }, 300);
210
216
  }
211
217
 
218
+ // Count real (non-"_empty", not in hide animation) banners to decide
219
+ // whether a Clear-all affordance is worth showing.
220
+ function countActiveBanners() {
221
+ if (!bannerContainer) return 0;
222
+ var banners = bannerContainer.querySelectorAll('.notif-banner');
223
+ var n = 0;
224
+ for (var i = 0; i < banners.length; i++) {
225
+ if (banners[i].classList.contains('hide')) continue;
226
+ if (banners[i].getAttribute('data-notif-id')) n++;
227
+ }
228
+ return n;
229
+ }
230
+
231
+ function ensureClearAllButton() {
232
+ if (!bannerContainer) return;
233
+ var existing = bannerContainer.querySelector('.notif-banner-clear-all');
234
+ if (countActiveBanners() >= 2) {
235
+ if (existing) return;
236
+ var btn = document.createElement('button');
237
+ btn.type = 'button';
238
+ btn.className = 'notif-banner-clear-all';
239
+ btn.innerHTML = iconHtml('x') + '<span>Clear all</span>';
240
+ btn.addEventListener('click', clearAllBanners);
241
+ bannerContainer.insertBefore(btn, bannerContainer.firstChild);
242
+ refreshIcons();
243
+ } else if (existing && existing.parentNode) {
244
+ existing.parentNode.removeChild(existing);
245
+ }
246
+ }
247
+
248
+ function clearAllBanners() {
249
+ var ws = getWs();
250
+ if (ws && ws.readyState === 1) {
251
+ ws.send(JSON.stringify({ type: 'notification_dismiss_all' }));
252
+ }
253
+ // Dismiss visually right away — the server will broadcast
254
+ // notification_dismissed_all too, but doing it optimistically makes
255
+ // the click feel instant.
256
+ if (bannerContainer) {
257
+ var banners = bannerContainer.querySelectorAll('.notif-banner[data-notif-id]');
258
+ for (var i = 0; i < banners.length; i++) removeBanner(banners[i]);
259
+ }
260
+ var btn = bannerContainer && bannerContainer.querySelector('.notif-banner-clear-all');
261
+ if (btn && btn.parentNode) btn.parentNode.removeChild(btn);
262
+ }
263
+
212
264
  function sendPermissionResponse(requestId, decision, slug) {
213
265
  var ws = getWs();
214
266
  if (ws && ws.readyState === 1) {
@@ -496,7 +496,7 @@ export function addAuthRequiredMessage(msg) {
496
496
  div.className = "auth-required-msg";
497
497
 
498
498
  var vendor = msg.vendor || "claude";
499
- var loginCmd = msg.loginCommand || (vendor === "codex" ? "codex --login" : "claude login");
499
+ var loginCmd = msg.loginCommand || (vendor === "codex" ? "codex login" : "claude login");
500
500
  var vendorName = msg.text || "Claude Code is not logged in.";
501
501
 
502
502
  var header = document.createElement("div");