clay-server 2.32.1-beta.1 → 2.33.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.
package/lib/daemon.js CHANGED
@@ -737,6 +737,24 @@ var relay = createServer({
737
737
  console.log("[daemon] Auto-continue on rate limit:", want, "(web)");
738
738
  return { ok: true, autoContinueOnRateLimit: want };
739
739
  },
740
+ onGetToolPalettes: function () {
741
+ return config.toolPalettes || {};
742
+ },
743
+ onSetToolPalette: function (paletteName, order, hidden) {
744
+ if (paletteName !== "session" && paletteName !== "mate") {
745
+ return { error: "Unknown palette" };
746
+ }
747
+ var safeOrder = Array.isArray(order)
748
+ ? order.filter(function (s) { return typeof s === "string"; })
749
+ : [];
750
+ var safeHidden = Array.isArray(hidden)
751
+ ? hidden.filter(function (s) { return typeof s === "string"; })
752
+ : [];
753
+ if (!config.toolPalettes) config.toolPalettes = {};
754
+ config.toolPalettes[paletteName] = { order: safeOrder, hidden: safeHidden };
755
+ saveConfig(config);
756
+ return { ok: true, palette: paletteName, order: safeOrder, hidden: safeHidden };
757
+ },
740
758
  onSetKeepAwake: function (value) {
741
759
  var want = !!value;
742
760
  config.keepAwake = want;
@@ -405,12 +405,28 @@ function attachEmail(ctx) {
405
405
  }
406
406
  }
407
407
 
408
+ // Whether any email capability is available for the active user.
409
+ // Used to gate the clay-email MCP so its tools are hidden from the model
410
+ // when the user hasn't registered any accounts and the server SMTP isn't
411
+ // configured (see issue #325 — we don't expose tools the user can't use).
412
+ function hasEmailCapability() {
413
+ try {
414
+ var accounts = emailAccounts.listAccounts(getActiveUserId());
415
+ if (accounts && accounts.length > 0) return true;
416
+ } catch (e) { /* fall through to SMTP check */ }
417
+ try {
418
+ if (smtp.isSmtpConfigured()) return true;
419
+ } catch (e) { /* ignore */ }
420
+ return false;
421
+ }
422
+
408
423
  return {
409
424
  handleEmailMessage: handleEmailMessage,
410
425
  getEmailContext: getEmailContext,
411
426
  getCheckedEmailAccounts: getCheckedEmailAccounts,
412
427
  getEmailDefaults: getEmailDefaults,
413
428
  createMcpDeps: createMcpDeps,
429
+ hasEmailCapability: hasEmailCapability,
414
430
  destroy: destroy,
415
431
  };
416
432
  }
package/lib/project.js CHANGED
@@ -540,6 +540,32 @@ function createProjectContext(opts) {
540
540
  return Object.keys(servers).length > 0 ? servers : undefined;
541
541
  })();
542
542
 
543
+ // Gate in-app MCP servers on the underlying capability actually being
544
+ // available. Without this, tools show up in every session's tool list
545
+ // even when the user can't use them, which wastes context and can cause
546
+ // the model to pick the wrong MCP when the user has another one
547
+ // configured (see issue #325).
548
+ //
549
+ // clay-browser -> only when the Chrome extension is connected
550
+ // clay-email -> only when the user has an account or server SMTP
551
+ function getLocalMcpServers() {
552
+ if (!mcpServers) return undefined;
553
+ var extWs = browserState._extensionWs;
554
+ var extConnected = !!(extWs && extWs.readyState === 1);
555
+ var emailAvailable = !!(_email && typeof _email.hasEmailCapability === "function" && _email.hasEmailCapability());
556
+ var keys = Object.keys(mcpServers);
557
+ var filtered = {};
558
+ var hasAny = false;
559
+ for (var i = 0; i < keys.length; i++) {
560
+ var name = keys[i];
561
+ if (name === "clay-browser" && !extConnected) continue;
562
+ if (name === "clay-email" && !emailAvailable) continue;
563
+ filtered[name] = mcpServers[name];
564
+ hasAny = true;
565
+ }
566
+ return hasAny ? filtered : undefined;
567
+ }
568
+
543
569
  // --- SDK bridge ---
544
570
  var sdk = createSDKBridge({
545
571
  cwd: cwd,
@@ -553,7 +579,7 @@ function createProjectContext(opts) {
553
579
  mateDisplayName: opts.mateDisplayName || "",
554
580
  isMate: isMate,
555
581
  dangerouslySkipPermissions: dangerouslySkipPermissions,
556
- mcpServers: mcpServers,
582
+ mcpServers: getLocalMcpServers,
557
583
  getRemoteMcpServers: function () { return _mcp.getMcpServers(); },
558
584
  clayPort: serverPort,
559
585
  clayTls: serverTls,
@@ -1125,11 +1151,14 @@ function createProjectContext(opts) {
1125
1151
  }
1126
1152
  }
1127
1153
 
1128
- // In-app MCP servers (debate, browser, email)
1129
- if (mcpServers) {
1130
- var inAppNames = Object.keys(mcpServers);
1154
+ // In-app MCP servers (debate, browser, email).
1155
+ // Use getLocalMcpServers() so clay-browser is hidden unless the
1156
+ // Chrome extension is currently connected (see issue #325).
1157
+ var localMcp = getLocalMcpServers();
1158
+ if (localMcp) {
1159
+ var inAppNames = Object.keys(localMcp);
1131
1160
  for (var i = 0; i < inAppNames.length; i++) {
1132
- extractServerTools(inAppNames[i], mcpServers[inAppNames[i]]);
1161
+ extractServerTools(inAppNames[i], localMcp[inAppNames[i]]);
1133
1162
  }
1134
1163
  }
1135
1164
 
@@ -1147,9 +1176,10 @@ function createProjectContext(opts) {
1147
1176
  return Promise.resolve(tools);
1148
1177
  },
1149
1178
  callTool: function (serverName, toolName, args) {
1150
- // Try in-app servers first
1151
- if (mcpServers && mcpServers[serverName]) {
1152
- var server = mcpServers[serverName];
1179
+ // Try in-app servers first (gated by extension connectivity for clay-browser).
1180
+ var localMcp = getLocalMcpServers();
1181
+ if (localMcp && localMcp[serverName]) {
1182
+ var server = localMcp[serverName];
1153
1183
  if (server.instance && server.instance._registeredTools && server.instance._registeredTools[toolName]) {
1154
1184
  var handler = server.instance._registeredTools[toolName].handler;
1155
1185
  if (typeof handler === "function") {
package/lib/public/app.js CHANGED
@@ -40,6 +40,8 @@ import { initPlaybook, openPlaybook, getPlaybooks, getPlaybookForTip, isComplete
40
40
  import { initSTT } from './modules/stt.js';
41
41
  import { initProfile, getProfileLang } from './modules/profile.js';
42
42
  import { initUserSettings } from './modules/user-settings.js';
43
+ import { initToolPalettes } from './modules/tool-palette.js';
44
+ import { initProjectSwitcher } from './modules/project-switcher.js';
43
45
  import { initAdmin, checkAdminAccess } from './modules/admin.js';
44
46
  import { initSessionSearch, toggleSearch, closeSearch, isSearchOpen, handleFindInSessionResults, onHistoryPrepended as onSessionSearchHistoryPrepended } from './modules/session-search.js';
45
47
  import { initTooltips, registerTooltip } from './modules/tooltip.js';
@@ -396,6 +398,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
396
398
  get projectList() { return getCachedProjects() || []; },
397
399
  availableBuiltins: function () { return store.get('cachedAvailableBuiltins') || []; },
398
400
  };
401
+ // Render the customizable tool palettes BEFORE initSidebar so the
402
+ // buttons exist in the DOM when sidebar.js (and later terminal.js /
403
+ // mcp-ui.js / etc.) attach click handlers by ID.
404
+ initToolPalettes();
399
405
  initSidebar(sidebarCtx);
400
406
  var wsGetter = function () { return _getWsRef(); };
401
407
  initMateSidebar(wsGetter);
@@ -441,6 +447,11 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
441
447
  },
442
448
  });
443
449
 
450
+ // Project switcher (Cmd/Ctrl+E). Init after command palette so its
451
+ // keydown listener registers later — capture-phase ordering doesn't
452
+ // matter here but it keeps related bootstrap steps adjacent.
453
+ initProjectSwitcher();
454
+
444
455
  // --- Connect overlay (animated ASCII logo) ---
445
456
  var asciiLogoCanvas = $("ascii-logo-canvas");
446
457
  initAsciiLogo(asciiLogoCanvas);
@@ -371,3 +371,189 @@
371
371
  display: none;
372
372
  }
373
373
  }
374
+
375
+ /* --- Project switcher (Cmd/Ctrl+E) ---
376
+ macOS Cmd+Tab-style quick switcher: centered modal with a darker
377
+ backdrop than the command palette, projects laid out horizontally
378
+ as icon+label tiles that wrap to multiple rows when there are many.
379
+ Reuses .cmd-palette shell so positioning/animations stay consistent. */
380
+ .project-switcher .cmd-palette-backdrop {
381
+ background: rgba(var(--shadow-rgb), 0.55);
382
+ }
383
+ .project-switcher-dialog {
384
+ top: 50%;
385
+ left: 50%;
386
+ transform: translate(-50%, -50%);
387
+ border-top: 1px solid var(--border);
388
+ border-radius: 16px;
389
+ width: min(820px, 85vw);
390
+ max-height: 80vh;
391
+ background: var(--bg-alt);
392
+ box-shadow: 0 20px 60px rgba(var(--shadow-rgb), 0.5);
393
+ animation: projSwitcherIn 0.12s ease-out;
394
+ }
395
+ @keyframes projSwitcherIn {
396
+ from { opacity: 0; transform: translate(-50%, -50%) scale(0.96); }
397
+ to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
398
+ }
399
+ .project-switcher-header {
400
+ padding: 14px 20px 6px;
401
+ font-size: 11px;
402
+ font-weight: 600;
403
+ color: var(--text-dimmer);
404
+ text-transform: uppercase;
405
+ letter-spacing: 0.5px;
406
+ }
407
+ .project-switcher-dialog .cmd-palette-results {
408
+ padding: 12px 16px;
409
+ display: flex;
410
+ flex-wrap: wrap;
411
+ gap: 8px;
412
+ justify-content: flex-start;
413
+ overflow-y: auto;
414
+ max-height: calc(80vh - 110px);
415
+ }
416
+ .project-switcher-item {
417
+ flex: 0 0 auto;
418
+ width: 96px;
419
+ height: 104px;
420
+ padding: 10px 6px;
421
+ display: flex;
422
+ flex-direction: column;
423
+ align-items: center;
424
+ justify-content: flex-start;
425
+ gap: 8px;
426
+ border: 2px solid transparent;
427
+ border-radius: 12px;
428
+ background: transparent;
429
+ cursor: pointer;
430
+ transition: background 0.12s, border-color 0.12s;
431
+ text-align: center;
432
+ }
433
+ /* Icon-strip-style treatment: rounded bg-alt fill that turns accent
434
+ on hover/active, and a 4-direction drop-shadow + white glow on the
435
+ emoji img for readability on either background. */
436
+ .project-switcher-item .cmd-palette-item-icon {
437
+ width: 44px;
438
+ height: 44px;
439
+ font-size: 28px;
440
+ line-height: 1;
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: center;
444
+ border-radius: 12px;
445
+ background: var(--bg-alt);
446
+ color: var(--text-secondary);
447
+ flex-shrink: 0;
448
+ transition: background 0.15s, color 0.15s;
449
+ }
450
+ .project-switcher-item .cmd-palette-item-icon .lucide {
451
+ width: 22px;
452
+ height: 22px;
453
+ }
454
+ .project-switcher-item .cmd-palette-item-icon img.emoji {
455
+ width: 26px;
456
+ height: 26px;
457
+ vertical-align: middle;
458
+ margin: 0;
459
+ filter:
460
+ drop-shadow(1px 0 0 rgba(0,0,0,0.25))
461
+ drop-shadow(-1px 0 0 rgba(0,0,0,0.25))
462
+ drop-shadow(0 1px 0 rgba(0,0,0,0.25))
463
+ drop-shadow(0 -1px 0 rgba(0,0,0,0.25))
464
+ drop-shadow(0 0 4px rgba(255,255,255,0.35));
465
+ }
466
+ .project-switcher-item:hover .cmd-palette-item-icon,
467
+ .project-switcher-item.active .cmd-palette-item-icon {
468
+ background: var(--accent);
469
+ color: #fff;
470
+ }
471
+ .project-switcher-avatar {
472
+ width: 100%;
473
+ height: 100%;
474
+ border-radius: 12px;
475
+ object-fit: cover;
476
+ }
477
+ .project-switcher-item .cmd-palette-item-body {
478
+ width: 100%;
479
+ display: block;
480
+ }
481
+ .project-switcher-item .cmd-palette-item-title-row {
482
+ display: flex;
483
+ flex-direction: column;
484
+ align-items: center;
485
+ gap: 2px;
486
+ width: 100%;
487
+ }
488
+ .project-switcher-item .cmd-palette-item-title {
489
+ font-size: 12px;
490
+ font-weight: 500;
491
+ line-height: 1.2;
492
+ color: var(--text-secondary);
493
+ max-width: 100%;
494
+ overflow: hidden;
495
+ text-overflow: ellipsis;
496
+ white-space: nowrap;
497
+ display: block;
498
+ }
499
+ .project-switcher-item:hover {
500
+ background: transparent;
501
+ }
502
+ .project-switcher-item.active {
503
+ border-color: var(--accent);
504
+ background: transparent;
505
+ }
506
+ .project-switcher-item.active .cmd-palette-item-title {
507
+ color: var(--text);
508
+ }
509
+
510
+ .project-switcher-current {
511
+ font-size: 9px;
512
+ font-weight: 600;
513
+ color: var(--text-dimmer);
514
+ text-transform: uppercase;
515
+ letter-spacing: 0.5px;
516
+ }
517
+
518
+ /* Disabled tile (e.g. worktree with worktreeAccessible: false). Not a
519
+ valid switch target, so skip highlight, skip click, visually dim. */
520
+ .project-switcher-item.disabled {
521
+ opacity: 0.35;
522
+ cursor: not-allowed;
523
+ }
524
+ .project-switcher-item.disabled:hover {
525
+ background: transparent;
526
+ }
527
+ .project-switcher-item.disabled.active {
528
+ border-color: transparent;
529
+ background: transparent;
530
+ }
531
+ .project-switcher-disabled-reason {
532
+ font-size: 9px;
533
+ font-weight: 500;
534
+ color: var(--text-dimmer);
535
+ font-style: italic;
536
+ max-width: 100%;
537
+ overflow: hidden;
538
+ text-overflow: ellipsis;
539
+ white-space: nowrap;
540
+ }
541
+
542
+ .project-switcher-footer {
543
+ padding: 10px 16px;
544
+ font-size: 11px;
545
+ color: var(--text-dimmer);
546
+ border-top: 1px solid var(--border-subtle);
547
+ display: flex;
548
+ justify-content: center;
549
+ }
550
+ .project-switcher-footer kbd {
551
+ display: inline-block;
552
+ padding: 1px 5px;
553
+ margin: 0 1px;
554
+ background: rgba(var(--overlay-rgb), 0.1);
555
+ border-radius: 4px;
556
+ font-size: 10px;
557
+ font-family: inherit;
558
+ color: var(--text-muted);
559
+ }
@@ -2,76 +2,51 @@
2
2
  File Browser
3
3
  ========================================================================== */
4
4
 
5
- /* --- Session actions --- */
5
+ /* --- Session actions ---
6
+ Compact icon+caption grid. Tile styles come from .palette-tile in
7
+ sidebar.css so the same tiles render correctly in both the active
8
+ grid here and the hidden-grid when edit-mode moves them. */
6
9
  #session-actions {
7
- display: flex;
8
- flex-direction: column;
10
+ display: grid;
11
+ grid-template-columns: repeat(4, 1fr);
12
+ gap: 4px;
9
13
  padding: 0 8px 4px;
10
14
  }
11
15
 
12
- #session-actions button {
13
- display: flex;
14
- align-items: center;
15
- gap: 8px;
16
- padding: 8px 12px;
17
- border-radius: 10px;
18
- border: none;
19
- background: transparent;
20
- color: var(--text-muted);
21
- font-family: inherit;
22
- font-size: 14px;
23
- font-weight: 600;
24
- cursor: pointer;
25
- margin-bottom: 0;
26
- transition: background 0.15s, color 0.15s;
27
- }
28
-
29
- #session-actions button .lucide { width: 16px; height: 16px; flex-shrink: 0; }
30
- #session-actions button:hover { background: var(--sidebar-hover); color: var(--text-secondary); }
31
- #session-actions button.active { background: var(--sidebar-hover); color: var(--text); }
32
-
16
+ /* Badge becomes a small corner indicator in icon-only grid */
33
17
  .sidebar-badge {
34
- background: rgba(var(--overlay-rgb), 0.1);
18
+ position: absolute;
19
+ top: 4px;
20
+ right: 4px;
21
+ background: rgba(var(--overlay-rgb), 0.15);
35
22
  color: var(--text-dimmer);
36
- font-size: 10px;
23
+ font-size: 9px;
37
24
  font-weight: 700;
38
- min-width: 16px;
39
- height: 16px;
40
- border-radius: 8px;
25
+ min-width: 14px;
26
+ height: 14px;
27
+ border-radius: 7px;
41
28
  display: inline-flex;
42
29
  align-items: center;
43
30
  justify-content: center;
44
- padding: 0 4px;
45
- margin-left: auto;
31
+ padding: 0 3px;
46
32
  line-height: 1;
33
+ pointer-events: none;
47
34
  }
48
35
  .sidebar-badge.hidden { display: none; }
49
36
 
37
+ /* Sticky-notes badge stays clickable in the icon grid — it toggles the
38
+ archive panel. The old inline "Open Archive" expand label doesn't fit
39
+ a corner dot, so it's gone; the cursor and hover color still signal
40
+ that the badge itself is interactive. */
50
41
  #sticky-notes-sidebar-count {
51
42
  cursor: pointer;
52
- overflow: hidden;
53
- transition: padding 0.2s ease, background 0.15s ease, color 0.15s ease;
54
- }
55
- #sticky-notes-sidebar-count::after {
56
- content: "Open Archive";
57
- display: inline-block;
58
- max-width: 0;
59
- overflow: hidden;
60
- white-space: nowrap;
61
- vertical-align: middle;
62
- margin-left: 0;
63
- opacity: 0;
64
- transition: max-width 0.2s ease, margin-left 0.2s ease, opacity 0.15s ease;
43
+ pointer-events: auto;
44
+ transition: background 0.15s ease, color 0.15s ease;
65
45
  }
66
46
  #sticky-notes-sidebar-count:hover {
67
47
  background: var(--accent);
68
48
  color: #fff;
69
49
  }
70
- #sticky-notes-sidebar-count:hover::after {
71
- max-width: 80px;
72
- margin-left: 4px;
73
- opacity: 1;
74
- }
75
50
 
76
51
  /* --- Panels --- */
77
52
  .sidebar-panel { flex: 1; overflow-y: auto; min-height: 0; }
@@ -95,8 +70,45 @@
95
70
  margin: 6px 6px 12px;
96
71
  display: flex;
97
72
  flex-direction: column;
73
+ /* Own the scroll: inner file-tree scrolls, titlebar/search/hint stay
74
+ anchored. Without this the whole panel (inherited overflow-y:auto
75
+ from .sidebar-panel) scrolled past the search bar. */
76
+ overflow: hidden;
98
77
  }
99
78
  #sidebar-panel-files.hidden { display: none; }
79
+ #sidebar-panel-files #file-tree {
80
+ flex: 1;
81
+ min-height: 0;
82
+ overflow-y: auto;
83
+ }
84
+
85
+ /* Keyboard hint footer — pinned below the tree, always visible. */
86
+ .fb-kb-hint {
87
+ flex-shrink: 0;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ gap: 6px;
92
+ padding: 6px 8px;
93
+ border-top: 1px solid var(--border-subtle);
94
+ font-size: 10px;
95
+ font-weight: 500;
96
+ color: var(--text-dimmer);
97
+ }
98
+ .fb-kb-hint kbd {
99
+ display: inline-block;
100
+ padding: 1px 5px;
101
+ margin: 0 1px;
102
+ background: var(--bg-alt);
103
+ border: 1px solid var(--border-subtle);
104
+ border-radius: 4px;
105
+ font-size: 10px;
106
+ font-family: inherit;
107
+ color: var(--text-muted);
108
+ }
109
+ .fb-kb-hint span {
110
+ opacity: 0.9;
111
+ }
100
112
 
101
113
  /* --- File browser titlebar --- */
102
114
  .fb-titlebar {
@@ -247,6 +259,18 @@
247
259
 
248
260
  .file-tree-item:hover { background: var(--sidebar-hover); color: var(--text); }
249
261
  .file-tree-item.active { background: var(--sidebar-active); color: var(--text); }
262
+ /* Keyboard focus ring (arrow-key navigation) — separate from .active
263
+ so the currently-opened file stays visually marked even while the
264
+ user is exploring elsewhere with the arrows. */
265
+ .file-tree-item.fb-kb-focus {
266
+ background: var(--sidebar-hover);
267
+ color: var(--text);
268
+ box-shadow: inset 0 0 0 1px var(--accent);
269
+ }
270
+ #file-tree:focus { outline: none; }
271
+ #file-tree:focus .file-tree-item.fb-kb-focus {
272
+ background: var(--sidebar-active);
273
+ }
250
274
  .file-tree-item .lucide { width: 14px; height: 14px; flex-shrink: 0; }
251
275
 
252
276
  .file-tree-name {
@@ -112,6 +112,37 @@
112
112
  flex-shrink: 0;
113
113
  }
114
114
 
115
+ /* Keyboard-shortcut hint above each switchable strip section. Tiny
116
+ pill with the platform-specific chord; clicking opens the switcher
117
+ in that mode, same as the keyboard shortcut itself. */
118
+ .icon-strip-hint {
119
+ flex-shrink: 0;
120
+ display: inline-flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ min-width: 28px;
124
+ padding: 2px 4px;
125
+ margin: 2px 0;
126
+ border: 1px solid var(--border-subtle);
127
+ background: var(--bg-alt);
128
+ color: var(--text-dimmer);
129
+ font-size: 10px;
130
+ font-weight: 600;
131
+ font-family: inherit;
132
+ border-radius: 6px;
133
+ cursor: pointer;
134
+ opacity: 0.7;
135
+ transition: opacity 0.15s, color 0.15s, background 0.15s;
136
+ }
137
+ .icon-strip-hint:hover {
138
+ opacity: 1;
139
+ color: var(--text-muted);
140
+ background: var(--bg);
141
+ }
142
+ .icon-strip-hint.hidden {
143
+ display: none;
144
+ }
145
+
115
146
  /* --- Scrollable project list area --- */
116
147
  .icon-strip-projects {
117
148
  width: 100%;
@@ -763,12 +794,23 @@
763
794
  height: 22px;
764
795
  }
765
796
 
766
- /* --- User avatars (circle, above my-avatar at bottom) --- */
767
- .icon-strip-users {
797
+ /* Mate section wrapper pinned just above the user-island at the
798
+ bottom of the strip. Groups the \u2318M hint pill with the users
799
+ list so the hint sits directly above the mate avatars rather than
800
+ floating up in the project section's flex flow. */
801
+ .icon-strip-mate-section {
768
802
  position: absolute;
769
803
  bottom: 74px; /* above user-island (58px + 8px bottom + 8px gap) */
770
804
  left: 50%;
771
805
  transform: translateX(-50%);
806
+ display: flex;
807
+ flex-direction: column;
808
+ align-items: center;
809
+ gap: 4px;
810
+ }
811
+
812
+ /* --- User avatars (circle, inside the mate section) --- */
813
+ .icon-strip-users {
772
814
  width: 56px;
773
815
  display: flex;
774
816
  flex-direction: column;
@@ -885,7 +885,10 @@
885
885
  #ask-mate-btn:hover { background: rgba(var(--overlay-rgb), 0.06); color: var(--text); }
886
886
  #ask-mate-btn:active { transform: scale(0.95); }
887
887
 
888
- /* Mate avatar overlay on @ button */
888
+ /* Mate avatar overlay on @ button.
889
+ Rendered desaturated and dim so it sits quietly in the corner of the
890
+ input area. Regains saturation on hover, so the user only sees the
891
+ actual mate identity when they're actively reaching for the button. */
889
892
  .ask-mate-avatar {
890
893
  position: absolute;
891
894
  bottom: 0;
@@ -895,12 +898,14 @@
895
898
  border-radius: 50%;
896
899
  border: 1.5px solid var(--input-bg);
897
900
  pointer-events: none;
898
- opacity: 0.55;
899
- transition: opacity 0.3s, transform 0.3s;
901
+ opacity: 0.45;
902
+ filter: grayscale(100%);
903
+ transition: opacity 0.3s, transform 0.3s, filter 0.3s;
900
904
  }
901
905
 
902
906
  #ask-mate-btn:hover .ask-mate-avatar {
903
- opacity: 0.85;
907
+ opacity: 0.9;
908
+ filter: grayscale(0%);
904
909
  }
905
910
 
906
911
  .ask-mate-avatar.fade-out {
@@ -909,7 +914,7 @@
909
914
  }
910
915
 
911
916
  .ask-mate-avatar.fade-in {
912
- opacity: 0.55;
917
+ opacity: 0.45;
913
918
  transform: scale(1);
914
919
  }
915
920
 
@@ -588,6 +588,15 @@
588
588
  border-right: none;
589
589
  overflow: hidden;
590
590
  flex-shrink: 0;
591
+ -webkit-user-select: none;
592
+ user-select: none;
593
+ }
594
+ /* Inputs inside the mate sidebar remain selectable. */
595
+ #mate-sidebar-column input,
596
+ #mate-sidebar-column textarea,
597
+ #mate-sidebar-column [contenteditable="true"] {
598
+ -webkit-user-select: text;
599
+ user-select: text;
591
600
  }
592
601
 
593
602
  #mate-sidebar-column.hidden {
@@ -1015,38 +1024,18 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
1015
1024
  text-overflow: ellipsis;
1016
1025
  }
1017
1026
 
1018
- #mate-sidebar-tools {
1019
- display: flex;
1020
- flex-direction: column;
1021
- padding: 8px 8px 4px;
1027
+ /* Mate sidebar tools: icon+caption grid, matches #session-actions.
1028
+ Tile styling comes from .palette-tile in sidebar.css. */
1029
+ #mate-sidebar-tools-wrap {
1022
1030
  flex-shrink: 0;
1023
1031
  border-bottom: 1px solid var(--border-subtle);
1032
+ padding-bottom: 4px;
1024
1033
  }
1025
-
1026
- #mate-sidebar-tools button {
1027
- display: flex;
1028
- align-items: center;
1029
- gap: 10px;
1030
- padding: 6px 10px;
1031
- border: none;
1032
- background: none;
1033
- color: var(--text-muted);
1034
- font-size: 13px;
1035
- font-weight: 600;
1036
- font-family: inherit;
1037
- border-radius: 8px;
1038
- cursor: pointer;
1039
- transition: background 0.15s, color 0.15s;
1040
- }
1041
-
1042
- #mate-sidebar-tools button:hover {
1043
- background: var(--sidebar-hover);
1044
- color: var(--text-secondary);
1045
- }
1046
-
1047
- #mate-sidebar-tools button svg {
1048
- width: 16px;
1049
- height: 16px;
1034
+ #mate-sidebar-tools {
1035
+ display: grid;
1036
+ grid-template-columns: repeat(4, 1fr);
1037
+ gap: 4px;
1038
+ padding: 0 8px 4px;
1050
1039
  }
1051
1040
 
1052
1041
  /* Knowledge editor panel (overlays main content) */