clay-server 2.33.0 → 2.33.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.
@@ -858,6 +858,7 @@
858
858
  </div>
859
859
  </div>
860
860
  <div class="user-island-actions">
861
+ <button id="user-theme-toggle-btn" title="Switch theme" aria-label="Switch theme"><i data-lucide="moon"></i></button>
861
862
  <button id="user-settings-btn" title="User settings"><i data-lucide="settings"></i></button>
862
863
  </div>
863
864
  </div>
@@ -928,23 +929,48 @@
928
929
  <div class="us-section" data-section="us-account">
929
930
  <h2>Account</h2>
930
931
  <div class="settings-card">
931
- <div class="settings-field-row">
932
- <div>
933
- <div class="settings-label">Username</div>
934
- <div class="settings-value" id="us-username">-</div>
932
+ <div class="us-account-hero">
933
+ <div class="us-account-avatar" aria-hidden="true">
934
+ <img id="us-account-avatar-img" class="us-account-avatar-img hidden" alt="">
935
+ <span id="us-account-avatar-fallback" class="us-account-avatar-fallback">?</span>
936
+ </div>
937
+ <div class="us-account-hero-copy">
938
+ <div class="us-account-name" id="us-account-name">-</div>
939
+ <div class="us-account-subline" id="us-account-subline">-</div>
940
+ <div class="settings-hint">Your profile is stored on this server and used across sessions.</div>
935
941
  </div>
936
942
  </div>
937
943
  </div>
938
944
  <h3>Security</h3>
939
945
  <div class="settings-card">
940
- <div class="settings-field">
941
- <label class="settings-label">PIN</label>
942
- <div class="settings-hint">6-digit code used to sign in to your account.</div>
943
- <div class="us-pin-row">
944
- <input type="password" class="us-text-input us-pin-input" id="us-pin-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="Enter new PIN" autocomplete="off">
945
- <button class="us-btn" id="us-pin-save" disabled>Change PIN</button>
946
+ <div class="settings-field-row">
947
+ <div>
948
+ <label class="settings-label">PIN</label>
949
+ <div class="settings-hint">Protect the web UI with a 6-digit PIN.</div>
946
950
  </div>
947
- <div class="us-pin-msg hidden" id="us-pin-msg"></div>
951
+ <button class="settings-btn-sm" id="us-pin-set-btn">Set PIN</button>
952
+ </div>
953
+ <div class="settings-field hidden" id="us-pin-form">
954
+ <div class="settings-pin-form-title" id="us-pin-form-title">Change PIN</div>
955
+ <div class="settings-pin-current-row" id="us-pin-current-row">
956
+ <label class="settings-label" for="us-pin-current-input">Current PIN</label>
957
+ <input type="password" class="us-text-input us-pin-input" id="us-pin-current-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="000000" autocomplete="off">
958
+ </div>
959
+ <div class="settings-pin-row">
960
+ <div class="settings-pin-field">
961
+ <label class="settings-label" for="us-pin-input">New PIN</label>
962
+ <input type="password" class="us-text-input us-pin-input" id="us-pin-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="000000" autocomplete="off">
963
+ </div>
964
+ <div class="settings-pin-field">
965
+ <label class="settings-label" for="us-pin-confirm-input">Confirm PIN</label>
966
+ <input type="password" class="us-text-input us-pin-input" id="us-pin-confirm-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="000000" autocomplete="off">
967
+ </div>
968
+ </div>
969
+ <div class="settings-pin-actions-row">
970
+ <button class="us-btn" id="us-pin-save" disabled>Save</button>
971
+ <button class="settings-btn-sm settings-btn-ghost" id="us-pin-cancel">Cancel</button>
972
+ </div>
973
+ <div class="settings-hint settings-pin-error hidden" id="us-pin-msg"></div>
948
974
  </div>
949
975
  </div>
950
976
  </div>
@@ -987,6 +1013,21 @@
987
1013
  <!-- Chat section -->
988
1014
  <div class="us-section" data-section="us-chat">
989
1015
  <h2>Chat</h2>
1016
+ <div class="settings-card">
1017
+ <div class="settings-field">
1018
+ <label class="settings-label" for="us-lang-select">Voice Input Language</label>
1019
+ <div class="settings-hint">Used by speech-to-text when you dictate messages.</div>
1020
+ <select id="us-lang-select" class="settings-select us-lang-select">
1021
+ <option value="en-US">English</option>
1022
+ <option value="ko-KR">Korean</option>
1023
+ <option value="ja-JP">Japanese</option>
1024
+ <option value="zh-CN">Chinese</option>
1025
+ <option value="es-ES">Spanish</option>
1026
+ <option value="fr-FR">French</option>
1027
+ <option value="de-DE">German</option>
1028
+ </select>
1029
+ </div>
1030
+ </div>
990
1031
  <div class="settings-card">
991
1032
  <label class="settings-toggle-row">
992
1033
  <div>
@@ -7,19 +7,15 @@ import { getStatusDot, getSendBtn } from './dom-refs.js';
7
7
  import { setSendBtnMode, blinkIO, setActivity } from './app-favicon.js';
8
8
  import { startLogoAnimation, stopLogoAnimation } from './ascii-logo.js';
9
9
  import { hasSendableContent } from './input.js';
10
- import { isNotifAlertEnabled } from './notifications.js';
11
10
  import { processMessage } from './app-messages.js';
12
11
  import { flushPendingExtMessages } from './app-misc.js';
13
12
  import { resetTerminals } from './terminal.js';
14
13
  import { closeDmUserPicker } from './sidebar-mates.js';
15
14
  import { openDm } from './app-dm.js';
16
15
 
17
- var wasConnected = false;
18
16
  var reconnectTimer = null;
19
17
  var reconnectDelay = 1000;
20
18
  var connectTimeoutId = null;
21
- var disconnectNotifTimer = null;
22
- var disconnectNotifShown = false;
23
19
  var connectOverlay = null;
24
20
 
25
21
  export function initConnection() {
@@ -162,24 +158,6 @@ export function connect() {
162
158
 
163
159
  newWs.onopen = function () {
164
160
  if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
165
- // Cancel pending "connection lost" notification if reconnected quickly
166
- if (disconnectNotifTimer) {
167
- clearTimeout(disconnectNotifTimer);
168
- disconnectNotifTimer = null;
169
- }
170
- // Only show "restored" notification if "lost" was actually shown
171
- var isMobileDevice = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
172
- (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
173
- if (wasConnected && disconnectNotifShown && !isMobileDevice && isNotifAlertEnabled() && !document.hasFocus() && "serviceWorker" in navigator && Notification.permission === "granted") {
174
- navigator.serviceWorker.ready.then(function (reg) {
175
- return reg.showNotification("Clay", {
176
- body: "Server connection restored",
177
- tag: "claude-disconnect",
178
- });
179
- }).catch(function () {});
180
- }
181
- disconnectNotifShown = false;
182
- wasConnected = true;
183
161
  setStatus("connected");
184
162
  reconnectDelay = 1000;
185
163
  if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
@@ -200,23 +178,6 @@ export function connect() {
200
178
  closeDmUserPicker();
201
179
  setStatus("disconnected");
202
180
  setActivity(null);
203
- // Delay "connection lost" notification by 5s to suppress brief disconnects
204
- if (!disconnectNotifTimer) {
205
- disconnectNotifTimer = setTimeout(function () {
206
- disconnectNotifTimer = null;
207
- disconnectNotifShown = true;
208
- var isMobileDevice = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
209
- (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
210
- if (!isMobileDevice && isNotifAlertEnabled() && !document.hasFocus() && "serviceWorker" in navigator && Notification.permission === "granted") {
211
- navigator.serviceWorker.ready.then(function (reg) {
212
- return reg.showNotification("Clay", {
213
- body: "Server connection lost",
214
- tag: "claude-disconnect",
215
- });
216
- }).catch(function () {});
217
- }
218
- }, 5000);
219
- }
220
181
  scheduleReconnect();
221
182
  };
222
183
 
@@ -37,7 +37,7 @@ import { handleMcpServersState } from './mcp-ui.js';
37
37
  import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, isSchedulerOpen, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './scheduler.js';
38
38
 
39
39
  // --- App module imports ---
40
- import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, addAuthRequiredMessage, showSuggestionChips } from './app-rendering.js';
40
+ import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, showSuggestionChips } from './app-rendering.js';
41
41
  import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot, updateCrossProjectBlink } from './app-favicon.js';
42
42
  import { setStatus } from './app-connection.js';
43
43
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
@@ -343,7 +343,7 @@ export function processMessage(msg) {
343
343
  } else {
344
344
  _miUpdate.currentModel = store.get('currentModel');
345
345
  }
346
- if (msg.vendor) _miUpdate.currentVendor = msg.vendor;
346
+ if (msg.vendor && !store.get('vendorSelectionLocked')) _miUpdate.currentVendor = msg.vendor;
347
347
  if (msg.availableVendors) _miUpdate.availableVendors = msg.availableVendors;
348
348
  if (msg.installedVendors) _miUpdate.installedVendors = msg.installedVendors;
349
349
  store.set(_miUpdate);
@@ -506,10 +506,16 @@ export function processMessage(msg) {
506
506
  }
507
507
  store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, vendorCapabilities: msg.capabilities || {} });
508
508
  if (msg.vendor) {
509
- store.set({ currentVendor: msg.vendor });
509
+ if (!store.get('vendorSelectionLocked') || msg.hasHistory) {
510
+ store.set({ currentVendor: msg.vendor });
511
+ }
512
+ if (msg.hasHistory) {
513
+ store.set({ vendorSelectionLocked: false });
514
+ }
510
515
  } else if (msg.hasHistory) {
511
516
  // Existing session without explicit vendor: reset to claude
512
517
  store.set({ currentVendor: "claude" });
518
+ store.set({ vendorSelectionLocked: false });
513
519
  } else if (!msg.hasHistory) {
514
520
  // New session without vendor: use mate's default vendor if in DM mode
515
521
  var _dmTarget = store.get('dmTargetUser');
@@ -523,6 +529,9 @@ export function processMessage(msg) {
523
529
  }
524
530
  }
525
531
  }
532
+ if (!msg.hasHistory && !msg.vendor) {
533
+ // Preserve explicit pre-message vendor choice on brand-new sessions.
534
+ }
526
535
  // Show vendor toggle only for new sessions (no history)
527
536
  var _vtw = document.getElementById("vendor-toggle-wrap");
528
537
  if (_vtw) {
@@ -914,7 +923,12 @@ export function processMessage(msg) {
914
923
  case "auth_required":
915
924
  removeMatePreThinking();
916
925
  setActivity(null);
917
- addAuthRequiredMessage(msg);
926
+ stopThinking();
927
+ markAllToolsDone();
928
+ closeToolGroup();
929
+ appendDelta((msg.text || "Authentication required.") + "\n");
930
+ setStatus("connected");
931
+ if (!store.get('loopActive')) enableMainInput();
918
932
  break;
919
933
 
920
934
  case "rate_limit":
@@ -10,6 +10,7 @@ import { openDm } from './app-dm.js';
10
10
  import { getCachedProjects } from './app-projects.js';
11
11
  import { switchProject } from './app-projects.js';
12
12
  import { mateAvatarUrl } from './avatar.js';
13
+ import { openTerminal } from './terminal.js';
13
14
  var notifications = [];
14
15
  var unreadCount = 0;
15
16
  var bannerContainer = null;
@@ -21,6 +22,8 @@ var badgeEl = null;
21
22
  // per-banner-instance and doesn't need to persist. The next server push
22
23
  // (next hour) acts as a fresh ping.
23
24
  var pendingUpdateMsg = null;
25
+ var activeAuthRequiredMsg = null;
26
+ var authReminderVisible = false;
24
27
 
25
28
  // ========================================================
26
29
  // Init
@@ -54,7 +57,14 @@ function showAllBanners() {
54
57
  // Re-add update banner if present (may be suppressed by recent dismiss)
55
58
  if (pendingUpdateMsg) showUpdateBanner(pendingUpdateMsg);
56
59
 
57
- // Check if any banner actually got rendered (update banner can be suppressed)
60
+ for (var i = 0; i < notifications.length; i++) {
61
+ showBanner(notifications[i], false);
62
+ }
63
+
64
+ if (activeAuthRequiredMsg) showAuthRequiredBanner(activeAuthRequiredMsg);
65
+ if (authReminderVisible) showLoginReminderBanner();
66
+
67
+ // Check if any banner actually got rendered (update/auth banners can be suppressed)
58
68
  var hasVisibleBanner = bannerContainer.children.length > 0;
59
69
  if (notifications.length === 0 && !hasVisibleBanner) {
60
70
  showBanner({
@@ -64,11 +74,6 @@ function showAllBanners() {
64
74
  body: "",
65
75
  slug: "",
66
76
  }, 3000);
67
- return;
68
- }
69
-
70
- for (var i = 0; i < notifications.length; i++) {
71
- showBanner(notifications[i], false);
72
77
  }
73
78
  }
74
79
 
@@ -80,20 +85,26 @@ function showBanner(notif, autoDismissMs) {
80
85
  if (!bannerContainer) return;
81
86
 
82
87
  var isEmpty = notif.id === "_empty";
83
- var projectIcon = isEmpty ? null : getProjectIcon(notif.slug);
84
- var projectName = isEmpty ? "" : getProjectName(notif.slug);
85
88
  var isPermission = notif.type === "permission_request" && notif.meta && notif.meta.requestId;
89
+ var isAuthRequired = notif.type === "auth_required";
90
+ var projectIcon = isEmpty ? null : getProjectIcon(notif.slug);
91
+ var projectName = isEmpty ? "" : (isAuthRequired ? "CLAY" : getProjectName(notif.slug));
86
92
  var mate = isEmpty ? null : getMateForNotification(notif);
87
93
 
88
94
  var banner = document.createElement("div");
89
- banner.className = "notif-banner" + (isPermission ? " notif-banner-permission" : "");
95
+ banner.className = "notif-banner" + (isAuthRequired ? " notif-banner-update notif-banner-auth" : isPermission ? " notif-banner-permission" : "");
90
96
  if (!isEmpty) banner.setAttribute("data-notif-id", notif.id);
97
+ if (isAuthRequired) banner.setAttribute("data-auth-banner", "true");
91
98
 
92
99
  var iconHtmlStr = mate
93
100
  ? '<img class="notif-banner-avatar" src="' + escapeHtml(mateAvatarUrl(mate, 32)) + '" alt="' + escapeHtml(mate.displayName || mate.name || "Mate") + '">'
94
- : projectIcon
101
+ : isAuthRequired
102
+ ? '<img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px">'
103
+ : projectIcon
95
104
  ? '<span class="notif-banner-emoji">' + projectIcon + '</span>'
96
- : iconHtml(isEmpty ? "check-circle" : "folder");
105
+ : isEmpty
106
+ ? '<img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px">'
107
+ : iconHtml("folder");
97
108
 
98
109
  // Format permission title as "Can I ..." style
99
110
  if (isPermission && notif.meta) {
@@ -112,6 +123,11 @@ function showBanner(notif, autoDismissMs) {
112
123
  '<button class="notif-banner-deny">No</button>' +
113
124
  '<button class="notif-banner-goto" title="Go to session">' + iconHtml("external-link") + '</button>' +
114
125
  '</div>';
126
+ } else if (isAuthRequired) {
127
+ actionsHtml =
128
+ '<div class="notif-banner-actions">' +
129
+ '<button class="notif-banner-auth-login notif-banner-update-now">Open terminal &amp; log in</button>' +
130
+ '</div>';
115
131
  }
116
132
 
117
133
  banner.innerHTML =
@@ -133,19 +149,22 @@ function showBanner(notif, autoDismissMs) {
133
149
  });
134
150
 
135
151
  if (!isEmpty) {
136
- // Click banner body -> navigate + dismiss
137
- banner.addEventListener("click", function (e) {
138
- if (e.target.closest(".notif-banner-close")) return;
139
- removeBanner(banner);
140
- dismissNotif(notif.id);
141
- navigateToNotification(notif);
142
- });
152
+ if (!isAuthRequired) {
153
+ // Click banner body -> navigate + dismiss
154
+ banner.addEventListener("click", function (e) {
155
+ if (e.target.closest(".notif-banner-close")) return;
156
+ removeBanner(banner);
157
+ dismissNotif(notif.id);
158
+ navigateToNotification(notif);
159
+ });
160
+ }
143
161
 
144
162
  // Close button -> dismiss
145
163
  var closeBtn = banner.querySelector(".notif-banner-close");
146
164
  if (closeBtn) {
147
165
  closeBtn.addEventListener("click", function (e) {
148
166
  e.stopPropagation();
167
+ if (isAuthRequired) activeAuthRequiredMsg = null;
149
168
  removeBanner(banner);
150
169
  dismissNotif(notif.id);
151
170
  });
@@ -191,6 +210,21 @@ function showBanner(notif, autoDismissMs) {
191
210
  });
192
211
  }
193
212
  }
213
+
214
+ if (isAuthRequired) {
215
+ var loginBtn = banner.querySelector(".notif-banner-auth-login");
216
+ if (loginBtn) {
217
+ loginBtn.addEventListener("click", function (e) {
218
+ e.stopPropagation();
219
+ activeAuthRequiredMsg = null;
220
+ removeBanner(banner);
221
+ dismissNotif(notif.id);
222
+ var authMeta = notif.meta || {};
223
+ startLoginCommand(authMeta.loginCommand || ((authMeta.vendor || "claude") === "codex" ? "codex login --device-auth" : "claude login"));
224
+ showLoginReminderBanner();
225
+ });
226
+ }
227
+ }
194
228
  }
195
229
 
196
230
  // Auto-dismiss (number = ms, false = stay)
@@ -201,6 +235,85 @@ function showBanner(notif, autoDismissMs) {
201
235
  }
202
236
  }
203
237
 
238
+ function startLoginCommand(loginCommand) {
239
+ var ws = getWs();
240
+ if (!ws || ws.readyState !== 1) return;
241
+ var termCommand = (loginCommand || "claude login") + "\n";
242
+ store.set({ pendingTermCommand: termCommand });
243
+ ws.send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
244
+ openTerminal();
245
+ }
246
+
247
+ function showLoginReminderBanner() {
248
+ if (!bannerContainer) return;
249
+ authReminderVisible = true;
250
+ var existing = bannerContainer.querySelector('[data-auth-reminder="true"]');
251
+ if (existing) removeBanner(existing);
252
+ var banner = document.createElement("div");
253
+ banner.className = "notif-banner notif-banner-update";
254
+ banner.setAttribute("data-notif-id", "_auth_reminder");
255
+ banner.setAttribute("data-auth-reminder", "true");
256
+ banner.innerHTML =
257
+ '<div class="notif-banner-icon">' + iconHtml("check-circle") + '</div>' +
258
+ '<div class="notif-banner-body">' +
259
+ '<div class="notif-banner-project">CLAY</div>' +
260
+ '<div class="notif-banner-title">Login started</div>' +
261
+ '<div class="notif-banner-text">After the login completes, open a new session so Clay picks up the fresh auth state.</div>' +
262
+ '<div class="notif-banner-actions">' +
263
+ '<button class="notif-banner-new-session notif-banner-update-now">Open new session</button>' +
264
+ '</div>' +
265
+ '</div>' +
266
+ '<button class="notif-banner-close">' + iconHtml("x") + '</button>';
267
+
268
+ bannerContainer.appendChild(banner);
269
+ refreshIcons();
270
+
271
+ requestAnimationFrame(function () {
272
+ banner.classList.add("show");
273
+ });
274
+
275
+ var newSessionBtn = banner.querySelector(".notif-banner-new-session");
276
+ if (newSessionBtn) {
277
+ newSessionBtn.addEventListener("click", function (e) {
278
+ e.stopPropagation();
279
+ authReminderVisible = false;
280
+ var ws = getWs();
281
+ if (ws && ws.readyState === 1) {
282
+ ws.send(JSON.stringify({ type: "new_session" }));
283
+ }
284
+ removeBanner(banner);
285
+ });
286
+ }
287
+
288
+ var closeBtn = banner.querySelector(".notif-banner-close");
289
+ if (closeBtn) {
290
+ closeBtn.addEventListener("click", function (e) {
291
+ e.stopPropagation();
292
+ authReminderVisible = false;
293
+ removeBanner(banner);
294
+ });
295
+ }
296
+ }
297
+
298
+ export function showAuthRequiredBanner(msg) {
299
+ if (!bannerContainer) return;
300
+ var vendor = (msg && (msg.vendor || (msg.meta && msg.meta.vendor))) || "claude";
301
+ var loginCommand = (msg && (msg.loginCommand || (msg.meta && msg.meta.loginCommand))) || (vendor === "codex" ? "codex login --device-auth" : "claude login");
302
+ activeAuthRequiredMsg = Object.assign({}, msg || {}, {
303
+ id: (msg && msg.id) || ("_auth_" + Date.now()),
304
+ type: "auth_required",
305
+ vendor: vendor,
306
+ loginCommand: loginCommand,
307
+ meta: Object.assign({}, msg && msg.meta ? msg.meta : {}, {
308
+ vendor: vendor,
309
+ loginCommand: loginCommand,
310
+ }),
311
+ });
312
+ var existing = bannerContainer.querySelector('[data-auth-banner="true"]');
313
+ if (existing) removeBanner(existing);
314
+ showBanner(activeAuthRequiredMsg, false);
315
+ }
316
+
204
317
  function removeBanner(banner) {
205
318
  if (!banner || !banner.parentNode) return;
206
319
  banner.classList.remove("show");
@@ -306,7 +419,7 @@ export function handleNotificationCreated(msg) {
306
419
  // Auto-dismiss if it's for the session the user is currently viewing
307
420
  var activeSession = store.get('activeSessionId') || null;
308
421
  console.log("[notif] created:", notif.type, "sessionId=" + notif.sessionId + "(" + typeof notif.sessionId + ")", "active=" + activeSession + "(" + typeof activeSession + ")", "match=" + (notif.sessionId == activeSession));
309
- if (notif.sessionId && String(notif.sessionId) === String(activeSession)) {
422
+ if (notif.type !== "auth_required" && notif.sessionId && String(notif.sessionId) === String(activeSession)) {
310
423
  dismissNotif(notif.id);
311
424
  return;
312
425
  }
@@ -315,7 +428,7 @@ export function handleNotificationCreated(msg) {
315
428
  unreadCount = msg.unreadCount;
316
429
  updateBadge();
317
430
 
318
- var _autoDismiss = notif.type === "permission_request" ? false : true;
431
+ var _autoDismiss = notif.type === "permission_request" || notif.type === "auth_required" ? false : true;
319
432
  showBanner(notif, _autoDismiss);
320
433
  }
321
434
 
@@ -410,7 +410,7 @@ export function initPanels() {
410
410
  if (vendor === (store.get('currentVendor') || "claude")) return;
411
411
  var installed = store.get('installedVendors') || [];
412
412
  if (installed.indexOf(vendor) === -1) return;
413
- store.set({ currentVendor: vendor, currentModel: "", currentModels: [] });
413
+ store.set({ currentVendor: vendor, currentModel: "", currentModels: [], vendorSelectionLocked: true });
414
414
  var ws = getWs();
415
415
  if (ws) ws.send(JSON.stringify({ type: "set_vendor", vendor: vendor }));
416
416
  }
@@ -7,7 +7,6 @@ import { getMessagesEl, getInputEl, getSendBtn } from './dom-refs.js';
7
7
  import { escapeHtml, copyToClipboard } from './utils.js';
8
8
  import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
9
9
  import { iconHtml, refreshIcons } from './icons.js';
10
- import { openTerminal } from './terminal.js';
11
10
  import { userAvatarUrl } from './avatar.js';
12
11
  import { closeToolGroup } from './tools.js';
13
12
  import { showImageModal, showPasteModal } from './app-misc.js';
@@ -491,80 +490,6 @@ export function addContextOverflowMessage(msg) {
491
490
  scrollToBottom();
492
491
  }
493
492
 
494
- export function addAuthRequiredMessage(msg) {
495
- var div = document.createElement("div");
496
- div.className = "auth-required-msg";
497
-
498
- var vendor = msg.vendor || "claude";
499
- var loginCmd = msg.loginCommand || (vendor === "codex" ? "codex login --device-auth" : "claude login");
500
- var vendorName = msg.text || "Claude Code is not logged in.";
501
-
502
- var header = document.createElement("div");
503
- header.className = "auth-required-header";
504
- header.textContent = vendorName;
505
- div.appendChild(header);
506
-
507
- var hint = document.createElement("div");
508
- hint.className = "auth-required-hint";
509
-
510
- if (msg.canAutoLogin) {
511
- if (msg.linuxUser) {
512
- hint.textContent = "Opening a terminal as " + msg.linuxUser + " to log in...";
513
- } else {
514
- hint.textContent = "Opening a terminal to log in...";
515
- }
516
- div.appendChild(hint);
517
-
518
- var guide = document.createElement("div");
519
- guide.className = "auth-required-guide";
520
- if (vendor === "codex") {
521
- guide.textContent = "Run the login command in the terminal and follow the instructions to authenticate.";
522
- } else {
523
- guide.textContent = "When a login URL appears in the terminal, click it to open in your browser. Do not press 'c' as it will try to open the browser on the server.";
524
- }
525
- div.appendChild(guide);
526
-
527
- var sessionHint = document.createElement("div");
528
- sessionHint.className = "auth-required-guide";
529
- sessionHint.textContent = "After logging in, start a new session to continue.";
530
- div.appendChild(sessionHint);
531
-
532
- var termCommand = loginCmd + "\n";
533
- var loginBtn = document.createElement("button");
534
- loginBtn.className = "auth-required-btn";
535
- loginBtn.textContent = "Open terminal & log in";
536
- loginBtn.addEventListener("click", function () {
537
- store.set({ pendingTermCommand: termCommand });
538
- getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
539
- openTerminal();
540
- });
541
- div.appendChild(loginBtn);
542
-
543
- addToMessages(div);
544
- scrollToBottom();
545
-
546
- var inputArea = document.getElementById("input-area");
547
- if (inputArea) inputArea.classList.add("hidden");
548
-
549
- if (!store.get('replayingHistory')) {
550
- store.set({ pendingTermCommand: termCommand });
551
- getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
552
- openTerminal();
553
- }
554
- } else {
555
- var adminVendorName = vendor.charAt(0).toUpperCase() + vendor.slice(1);
556
- hint.textContent = "Please ask an administrator to log in to " + adminVendorName + ".";
557
- div.appendChild(hint);
558
- addToMessages(div);
559
- scrollToBottom();
560
-
561
- var inputEl = getInputEl();
562
- inputEl.disabled = true;
563
- inputEl.placeholder = "Login required. Start a new session after logging in.";
564
- getSendBtn().disabled = true;
565
- }
566
- }
567
-
568
493
  // --- Pre-thinking (instant dots before server responds) ---
569
494
 
570
495
  export function showClaudePreThinking() {
@@ -228,6 +228,7 @@ export function sendMessage() {
228
228
  // Hide vendor toggle after first message (vendor is locked to this session)
229
229
  var _vtw2 = document.getElementById("vendor-toggle-wrap");
230
230
  if (_vtw2) { _vtw2.classList.remove("hidden"); _vtw2.classList.add("locked"); }
231
+ store.set({ vendorSelectionLocked: false });
231
232
 
232
233
  // Show pre-thinking dots before server responds
233
234
  if (ctx.isMateDm && ctx.isMateDm()) {
@@ -3,7 +3,7 @@
3
3
  // Avatar generated via DiceBear API (deterministic SVG from seed)
4
4
 
5
5
  import { iconHtml, refreshIcons } from './icons.js';
6
- import { setSTTLang, getSTTLang } from './stt.js';
6
+ import { setSTTLang } from './stt.js';
7
7
  import { avatarUrl, mateAvatarUrl, AVATAR_STYLES } from './avatar.js';
8
8
 
9
9
  var ctx;
@@ -13,16 +13,6 @@ var popoverEl = null;
13
13
  var saveTimer = null;
14
14
  var previewSeed = '';
15
15
 
16
- var LANGUAGES = [
17
- { code: 'en-US', name: 'English' },
18
- { code: 'ko-KR', name: 'Korean' },
19
- { code: 'ja-JP', name: 'Japanese' },
20
- { code: 'zh-CN', name: 'Chinese' },
21
- { code: 'es-ES', name: 'Spanish' },
22
- { code: 'fr-FR', name: 'French' },
23
- { code: 'de-DE', name: 'German' },
24
- ];
25
-
26
16
  // AVATAR_STYLES imported from avatar.js
27
17
 
28
18
  var COLORS = [
@@ -295,7 +285,6 @@ function showPopover() {
295
285
  popoverEl.className = 'profile-popover';
296
286
 
297
287
  var displayName = profile.name || '';
298
- var currentLang = profile.lang || 'en-US';
299
288
  var currentColor = profile.avatarColor || '#7c3aed';
300
289
  var currentStyle = profile.avatarStyle || 'thumbs';
301
290
  var seed = getAvatarSeed();
@@ -325,18 +314,6 @@ function showPopover() {
325
314
  html += '<input type="text" class="profile-field-input" id="profile-name-input" value="' + escapeAttr(displayName) + '" placeholder="Enter your name..." maxlength="50" spellcheck="false" autocomplete="off">';
326
315
  html += '</div>';
327
316
 
328
- // Language dropdown
329
- html += '<div class="profile-field">';
330
- html += '<label class="profile-field-label">Language <span class="profile-field-hint">for voice input</span></label>';
331
- html += '<select class="profile-field-select" id="profile-lang-select">';
332
- for (var i = 0; i < LANGUAGES.length; i++) {
333
- var l = LANGUAGES[i];
334
- var sel = (currentLang === l.code) ? ' selected' : '';
335
- html += '<option value="' + l.code + '"' + sel + '>' + l.name + '</option>';
336
- }
337
- html += '</select>';
338
- html += '</div>';
339
-
340
317
  // Avatar picker
341
318
  html += '<div class="profile-field">';
342
319
  html += '<label class="profile-field-label">Avatar <button class="profile-shuffle-btn" title="Shuffle">' + iconHtml('shuffle') + '</button></label>';
@@ -401,13 +378,6 @@ function showPopover() {
401
378
  nameInput.addEventListener('keyup', function(e) { e.stopPropagation(); });
402
379
  nameInput.addEventListener('keypress', function(e) { e.stopPropagation(); });
403
380
 
404
- // Language dropdown
405
- popoverEl.querySelector('#profile-lang-select').addEventListener('change', function(e) {
406
- profile.lang = e.target.value;
407
- setSTTLang(profile.lang);
408
- debouncedSave();
409
- });
410
-
411
381
  // Avatar upload button
412
382
  var uploadBtn = popoverEl.querySelector('.profile-avatar-upload');
413
383
  var fileInput = popoverEl.querySelector('#profile-avatar-file');