clay-server 5.1.0-beta.2 → 5.1.0-beta.3
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/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +8 -37
- package/lib/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-sync.js +1 -1
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +12 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +0 -7
- package/lib/project-debate.js +3 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +57 -184
- package/lib/project.js +3 -3
- package/lib/public/app.js +6 -37
- package/lib/public/css/input.css +3 -1
- package/lib/public/css/menus.css +3 -0
- package/lib/public/css/overlays.css +7 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/server-settings-system.css +125 -0
- package/lib/public/css/session-github.css +25 -16
- package/lib/public/css/settings-ui.css +247 -22
- package/lib/public/css/shared-browser.css +7 -3
- package/lib/public/css/title-bar.css +5 -1
- package/lib/public/index.html +173 -447
- package/lib/public/modules/account-island.js +9 -7
- package/lib/public/modules/admin-settings-email.js +197 -0
- package/lib/public/modules/admin-settings-invitations.js +183 -0
- package/lib/public/modules/admin-settings-projects.js +198 -0
- package/lib/public/modules/admin-settings-ui.js +100 -0
- package/lib/public/modules/admin-settings-users.js +331 -0
- package/lib/public/modules/admin.js +19 -949
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-messages.js +13 -52
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/avatar.js +6 -1
- package/lib/public/modules/context-sources.js +27 -18
- package/lib/public/modules/default-ai.js +14 -7
- package/lib/public/modules/header-tui-font.js +10 -20
- package/lib/public/modules/home-mate-avatar-editor.js +2 -2
- package/lib/public/modules/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- package/lib/public/modules/notification-settings.js +81 -0
- package/lib/public/modules/notifications.js +44 -28
- package/lib/public/modules/personal-settings-dialog.js +51 -0
- package/lib/public/modules/profile.js +15 -6
- package/lib/public/modules/project-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- package/lib/public/modules/server-settings-controls.js +316 -0
- package/lib/public/modules/server-settings-environment.js +194 -0
- package/lib/public/modules/server-settings-navigation.js +39 -0
- package/lib/public/modules/server-settings-state.js +54 -0
- package/lib/public/modules/server-settings.js +116 -788
- package/lib/public/modules/server-storage-settings.js +84 -0
- package/lib/public/modules/session-github.js +2 -1
- package/lib/public/modules/session-presence-ui.js +1 -1
- package/lib/public/modules/settings-env-values.js +34 -0
- package/lib/public/modules/settings-select.js +121 -0
- package/lib/public/modules/shared-browser.js +22 -12
- package/lib/public/modules/stt-picker.js +14 -5
- package/lib/public/modules/terminal-prefs.js +22 -0
- package/lib/public/modules/theme.js +88 -52
- package/lib/public/modules/user-settings-account.js +318 -0
- package/lib/public/modules/user-settings-appearance.js +55 -0
- package/lib/public/modules/user-settings-editors.js +24 -0
- package/lib/public/modules/user-settings-integrations.js +104 -0
- package/lib/public/modules/user-settings-layout.js +80 -0
- package/lib/public/modules/user-settings-preferences.js +48 -0
- package/lib/public/modules/user-settings-profile.js +53 -0
- package/lib/public/modules/user-settings-themes.js +38 -0
- package/lib/public/modules/user-settings-voice.js +75 -0
- package/lib/public/modules/user-settings.js +53 -480
- package/lib/public/style.css +4 -0
- package/lib/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-appearance-settings.js +71 -0
- package/lib/server-linear.js +14 -8
- package/lib/server-mate-instructions.js +2 -2
- package/lib/server-mates.js +2 -1
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-settings.js +4 -0
- package/lib/server-storage.js +57 -0
- package/lib/server.js +10 -2
- package/lib/sessions.js +3 -4
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users.js +0 -4
- package/lib/ws-schema.js +2 -4
- package/lib/yoke/acp-process-manager.js +2 -2
- package/lib/yoke/adapters/codex.js +12 -52
- package/lib/yoke/codex-app-server.js +2 -2
- package/lib/yoke/codex-model-catalog.js +52 -0
- package/lib/yoke/codex-rate-limits.js +26 -0
- package/package.json +3 -3
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { initPersonalAppearance } from './user-settings-appearance.js';
|
|
2
|
+
import { initPersonalVoice } from './user-settings-voice.js';
|
|
3
|
+
import { isolateSettingsDialog } from './server-settings-state.js';
|
|
4
|
+
import { initPersonalProfile } from './user-settings-profile.js';
|
|
5
|
+
import { initPersonalEditors } from './user-settings-editors.js';
|
|
6
|
+
import { initPersonalPreferences } from './user-settings-preferences.js';
|
|
7
|
+
import { showPinForm, hidePinForm, populateAccount, savePin, renderEmailSettings } from './user-settings-account.js';
|
|
8
|
+
import { buildUserSettingsLayout, resolveUserSettingsPage, loadUserSettingsExtras, finishUserSettingsExtras, guardUserSettingsLeave } from './user-settings-layout.js';
|
|
9
|
+
import { enhanceSettingsSelects } from './settings-select.js';
|
|
10
|
+
import { loadNotificationSettings } from './notification-settings.js';
|
|
2
11
|
// user-settings.js — Modal dialog for user settings
|
|
3
12
|
// Account management and logout
|
|
4
13
|
|
|
5
14
|
import { refreshIcons } from './icons.js';
|
|
6
|
-
import {
|
|
7
|
-
import { getChatLayout, mountThemePicker, setChatLayout } from './theme.js';
|
|
8
|
-
import { applyTerminalFont } from './terminal-prefs.js';
|
|
9
|
-
import { showEmailSetupModal, getEmailAccountListCache } from './context-sources.js';
|
|
10
|
-
import { saveSpeechLanguage, speechLanguages } from './stt-settings.js';
|
|
11
|
-
import { userAvatarUrl } from './avatar.js';
|
|
15
|
+
import { showEmailSetupModal } from './context-sources.js';
|
|
12
16
|
import { store } from './store.js';
|
|
13
|
-
import {
|
|
14
|
-
import { initCapsulePreference, syncCapsulePreference } from './capsule-preference.js';
|
|
17
|
+
import { initCapsulePreference } from './capsule-preference.js';
|
|
15
18
|
|
|
16
|
-
var ctx = null;
|
|
17
19
|
var settingsEl = null;
|
|
18
20
|
var openBtn = null;
|
|
19
21
|
var closeBtn = null;
|
|
@@ -23,17 +25,10 @@ var sections = null;
|
|
|
23
25
|
var pinForm = null;
|
|
24
26
|
var pinSetBtn = null;
|
|
25
27
|
var pinCancelBtn = null;
|
|
26
|
-
var pinEnabled = false;
|
|
27
|
-
var currentProfile = null;
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
export function initUserSettings(
|
|
31
|
-
|
|
32
|
-
var select = document.getElementById('us-lang-select');
|
|
33
|
-
if (select) select.value = event.detail;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
ctx = appCtx;
|
|
30
|
+
export function initUserSettings() {
|
|
31
|
+
buildUserSettingsLayout();
|
|
37
32
|
settingsEl = document.getElementById('user-settings');
|
|
38
33
|
openBtn = document.getElementById('user-settings-btn');
|
|
39
34
|
closeBtn = document.getElementById('user-settings-close');
|
|
@@ -42,7 +37,7 @@ export function initUserSettings(appCtx) {
|
|
|
42
37
|
if (!settingsEl || !openBtn) return;
|
|
43
38
|
|
|
44
39
|
initCapsulePreference();
|
|
45
|
-
|
|
40
|
+
|
|
46
41
|
|
|
47
42
|
navItems = settingsEl.querySelectorAll('.us-nav-item');
|
|
48
43
|
sections = settingsEl.querySelectorAll('.us-section');
|
|
@@ -65,15 +60,20 @@ export function initUserSettings(appCtx) {
|
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
document.addEventListener('keydown', function (e) {
|
|
68
|
-
if (e.key === '
|
|
69
|
-
|
|
63
|
+
if (e.key === 'Tab' && isUserSettingsOpen() && settingsEl.contains(document.activeElement)) {
|
|
64
|
+
var items = Array.from(settingsEl.querySelectorAll('button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),a[href]')).filter(function (el) {return el.getClientRects().length && el.tabIndex !== -1;});
|
|
65
|
+
var first=items[0],last=items[items.length-1];
|
|
66
|
+
if(e.shiftKey && document.activeElement===first){e.preventDefault();last.focus();}else if(!e.shiftKey && document.activeElement===last){e.preventDefault();first.focus();}
|
|
67
|
+
}
|
|
68
|
+
if (e.key === 'Escape' && isUserSettingsOpen() && !e.defaultPrevented && !store.get('speechPopup') && !document.querySelector('.admin-modal-overlay, #settings-confirm, .avatar-positioner-overlay, #email-setup-modal')) {
|
|
69
|
+
e.preventDefault();closeUserSettings();
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
for (var i = 0; i < navItems.length; i++) {
|
|
74
74
|
navItems[i].addEventListener('click', function () {
|
|
75
75
|
var section = this.dataset.section;
|
|
76
|
-
switchSection(section);
|
|
76
|
+
guardUserSettingsLeave(function () { switchSection(section); });
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -81,7 +81,7 @@ export function initUserSettings(appCtx) {
|
|
|
81
81
|
var navDropdown = document.getElementById('user-settings-nav-dropdown');
|
|
82
82
|
if (navDropdown) {
|
|
83
83
|
navDropdown.addEventListener('change', function () {
|
|
84
|
-
|
|
84
|
+
var value = this.value; guardUserSettingsLeave(function () {switchSection(value);});
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -96,7 +96,7 @@ export function initUserSettings(appCtx) {
|
|
|
96
96
|
function validatePin() {
|
|
97
97
|
var validNew = /^\d{6}$/.test(pinInput.value);
|
|
98
98
|
var validConfirm = /^\d{6}$/.test(pinConfirmInput.value);
|
|
99
|
-
var validCurrent = !
|
|
99
|
+
var validCurrent = !store.get('userPinEnabled') || (pinCurrentInput && /^\d{6}$/.test(pinCurrentInput.value));
|
|
100
100
|
pinSave.disabled = !(validNew && validConfirm && validCurrent && pinInput.value === pinConfirmInput.value);
|
|
101
101
|
}
|
|
102
102
|
pinInput.addEventListener('input', validatePin);
|
|
@@ -158,122 +158,15 @@ export function initUserSettings(appCtx) {
|
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
speechLanguages.forEach(function (language) {
|
|
165
|
-
var option = document.createElement('option'); option.value = language[0]; option.textContent = language[1]; langSelect.appendChild(option);
|
|
166
|
-
});
|
|
167
|
-
langSelect.addEventListener('change', function () {
|
|
168
|
-
var select = this; select.disabled = true;
|
|
169
|
-
saveSpeechLanguage(select.value).catch(function () {
|
|
170
|
-
select.value = store.get('speechLang') || 'en-US'; showToast('Could not save voice language');
|
|
171
|
-
}).finally(function () { select.disabled = false; });
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Auto-continue toggle
|
|
176
|
-
var autoContinueToggle = document.getElementById('us-auto-continue');
|
|
177
|
-
if (autoContinueToggle) {
|
|
178
|
-
autoContinueToggle.addEventListener('change', function () {
|
|
179
|
-
fetch('/api/user/auto-continue', {
|
|
180
|
-
method: 'PUT',
|
|
181
|
-
headers: { 'Content-Type': 'application/json' },
|
|
182
|
-
body: JSON.stringify({ enabled: this.checked }),
|
|
183
|
-
}).then(function (r) { return r.json(); }).then(function (data) {
|
|
184
|
-
if (data.ok) showToast(data.autoContinueOnRateLimit ? 'Auto-continue on' : 'Auto-continue off');
|
|
185
|
-
}).catch(function () {});
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Claude open mode toggle: GUI (default) vs TUI. The server persists the
|
|
190
|
-
// pref per user; the WS broadcast `claude_open_mode_changed` keeps the
|
|
191
|
-
// store and other tabs in sync.
|
|
192
|
-
var claudeOpenModeToggle = document.getElementById('us-claude-open-mode');
|
|
193
|
-
if (claudeOpenModeToggle) {
|
|
194
|
-
claudeOpenModeToggle.checked = store.get('claudeOpenMode') === 'tui';
|
|
195
|
-
claudeOpenModeToggle.addEventListener('change', function () {
|
|
196
|
-
var want = this.checked ? 'tui' : 'gui';
|
|
197
|
-
var ws = getWs();
|
|
198
|
-
if (ws && ws.readyState === 1) {
|
|
199
|
-
ws.send(JSON.stringify({ type: 'set_claude_open_mode', value: want }));
|
|
200
|
-
showToast(want === 'tui' ? 'Claude opens as terminal' : 'Claude opens as chat');
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Claude auto-approve allow-list: textarea-based editor that round-trips
|
|
206
|
-
// through the server (which writes ~/.claude/settings.json).
|
|
207
|
-
var allowTextarea = document.getElementById('us-claude-allow-list');
|
|
208
|
-
var allowSaveBtn = document.getElementById('us-claude-allow-save');
|
|
209
|
-
var allowStatus = document.getElementById('us-claude-allow-status');
|
|
210
|
-
var allowManagedEl = document.getElementById('us-claude-allow-managed');
|
|
211
|
-
if (allowTextarea && allowSaveBtn) {
|
|
212
|
-
// Fetch current state on open. The response (`claude_allow_list`)
|
|
213
|
-
// populates both the textarea and the managed-defaults readout.
|
|
214
|
-
var ws0 = getWs();
|
|
215
|
-
if (ws0 && ws0.readyState === 1) {
|
|
216
|
-
ws0.send(JSON.stringify({ type: 'get_claude_allow_list' }));
|
|
217
|
-
}
|
|
218
|
-
allowSaveBtn.addEventListener('click', function () {
|
|
219
|
-
var lines = allowTextarea.value.split('\n').map(function (l) { return l.trim(); }).filter(Boolean);
|
|
220
|
-
var ws = getWs();
|
|
221
|
-
if (ws && ws.readyState === 1) {
|
|
222
|
-
ws.send(JSON.stringify({ type: 'set_claude_user_allow_list', patterns: lines }));
|
|
223
|
-
if (allowStatus) {
|
|
224
|
-
allowStatus.textContent = 'Saving...';
|
|
225
|
-
allowStatus.classList.remove('error');
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// This legacy preference now controls only project-local Mate DM entry
|
|
232
|
-
// points. Home, mentions, creation, debates, and Mate data stay available.
|
|
233
|
-
var matesEnabledToggle = document.getElementById('us-mates-enabled');
|
|
234
|
-
if (matesEnabledToggle) {
|
|
235
|
-
matesEnabledToggle.addEventListener('change', function () {
|
|
236
|
-
var want = !!this.checked;
|
|
237
|
-
applyProjectMateDmsClass(want);
|
|
238
|
-
store.set({ projectMateDmsEnabled: want });
|
|
239
|
-
fetch('/api/user/mates-enabled', {
|
|
240
|
-
method: 'PUT',
|
|
241
|
-
headers: { 'Content-Type': 'application/json' },
|
|
242
|
-
body: JSON.stringify({ enabled: want }),
|
|
243
|
-
}).then(function (r) { return r.json(); }).then(function (data) {
|
|
244
|
-
if (data.ok) showToast(data.matesEnabled ? 'Mate DMs shown in projects' : 'Mate DMs hidden in projects');
|
|
245
|
-
}).catch(function () {});
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
mountThemePicker(document.getElementById('us-theme-picker'));
|
|
161
|
+
initPersonalPreferences();
|
|
162
|
+
initPersonalAppearance();
|
|
163
|
+
initPersonalVoice();
|
|
250
164
|
|
|
251
165
|
// Terminal font picker lives in the title bar (visible only while a
|
|
252
166
|
// TUI session is active). See lib/public/modules/header-tui-font.js.
|
|
253
167
|
// We still seed terminal-prefs from /api/profile below so the
|
|
254
168
|
// controls populate correctly when they appear.
|
|
255
169
|
|
|
256
|
-
// Layout switcher (Bubble / Channel)
|
|
257
|
-
var layoutSwitcher = document.getElementById('us-layout-switcher');
|
|
258
|
-
if (layoutSwitcher) {
|
|
259
|
-
var layoutBtns = layoutSwitcher.querySelectorAll('.layout-option');
|
|
260
|
-
for (var li = 0; li < layoutBtns.length; li++) {
|
|
261
|
-
layoutBtns[li].addEventListener('click', function () {
|
|
262
|
-
var layout = this.dataset.layout;
|
|
263
|
-
setChatLayout(layout);
|
|
264
|
-
for (var lj = 0; lj < layoutBtns.length; lj++) {
|
|
265
|
-
layoutBtns[lj].classList.toggle('selected', layoutBtns[lj].dataset.layout === layout);
|
|
266
|
-
}
|
|
267
|
-
// Save to server
|
|
268
|
-
fetch('/api/user/chat-layout', {
|
|
269
|
-
method: 'PUT',
|
|
270
|
-
headers: { 'Content-Type': 'application/json' },
|
|
271
|
-
body: JSON.stringify({ layout: layout }),
|
|
272
|
-
}).catch(function () {});
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
170
|
// Logout button
|
|
278
171
|
var logoutBtn = document.getElementById('us-logout-btn');
|
|
279
172
|
if (logoutBtn) {
|
|
@@ -286,32 +179,37 @@ export function initUserSettings(appCtx) {
|
|
|
286
179
|
});
|
|
287
180
|
}
|
|
288
181
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
182
|
+
initPersonalEditors();
|
|
183
|
+
initPersonalProfile();
|
|
184
|
+
|
|
185
|
+
document.querySelectorAll('[data-email-provider]').forEach(function (button) {
|
|
186
|
+
button.addEventListener('click', function () { showEmailSetupModal(button.dataset.emailProvider, document.getElementById("us-integration-setup")); });
|
|
187
|
+
});
|
|
296
188
|
}
|
|
297
189
|
|
|
298
190
|
// Optional `initialSection` lets callers deep-link directly into a specific
|
|
299
191
|
// settings tab (e.g. the disabled-Mates picker button passing 'us-mates').
|
|
300
192
|
// Falls back to Account so existing callers and the icon click are unchanged.
|
|
301
193
|
export function openUserSettings(initialSection) {
|
|
302
|
-
|
|
194
|
+
if(!isUserSettingsOpen())store.set({ userSettingsReturnFocus: document.activeElement, userSettingsRestore: isolateSettingsDialog(settingsEl) });
|
|
195
|
+
|
|
303
196
|
settingsEl.classList.remove('hidden');
|
|
304
197
|
openBtn.classList.add('active');
|
|
305
198
|
refreshIcons(settingsEl);
|
|
306
199
|
populateAccount();
|
|
307
|
-
hidePinForm();
|
|
308
200
|
switchSection(initialSection || 'us-account');
|
|
201
|
+
enhanceSettingsSelects(settingsEl);
|
|
202
|
+
var title=settingsEl.querySelector(".us-section.active h2"); if(title){title.tabIndex=-1;title.focus();}
|
|
309
203
|
}
|
|
310
204
|
|
|
311
205
|
export function closeUserSettings() {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
206
|
+
guardUserSettingsLeave(function () {
|
|
207
|
+
finishUserSettingsExtras();
|
|
208
|
+
settingsEl.classList.add('hidden');
|
|
209
|
+
var restore=store.get('userSettingsRestore');if(restore)restore();store.set({userSettingsRestore:null});
|
|
210
|
+
openBtn.classList.remove('active');
|
|
211
|
+
var focus = store.get('userSettingsReturnFocus'); if (focus && focus.isConnected) focus.focus();
|
|
212
|
+
});
|
|
315
213
|
}
|
|
316
214
|
|
|
317
215
|
export function isUserSettingsOpen() {
|
|
@@ -319,356 +217,31 @@ export function isUserSettingsOpen() {
|
|
|
319
217
|
}
|
|
320
218
|
|
|
321
219
|
function switchSection(sectionName) {
|
|
220
|
+
sectionName = resolveUserSettingsPage(sectionName);
|
|
221
|
+
loadUserSettingsExtras(sectionName);
|
|
322
222
|
for (var i = 0; i < navItems.length; i++) {
|
|
323
223
|
navItems[i].classList.toggle('active', navItems[i].dataset.section === sectionName);
|
|
224
|
+
if(navItems[i].dataset.section===sectionName)navItems[i].setAttribute('aria-current','page');else navItems[i].removeAttribute('aria-current');
|
|
324
225
|
}
|
|
325
226
|
for (var j = 0; j < sections.length; j++) {
|
|
326
227
|
sections[j].classList.toggle('active', sections[j].dataset.section === sectionName);
|
|
327
228
|
}
|
|
328
229
|
var navDropdown = document.getElementById('user-settings-nav-dropdown');
|
|
329
230
|
if (navDropdown) navDropdown.value = sectionName;
|
|
330
|
-
if (sectionName === 'us-
|
|
231
|
+
if (sectionName === 'us-notifications') loadNotificationSettings();
|
|
232
|
+
if (sectionName === 'us-integrations') renderEmailSettings();
|
|
233
|
+
var heading=settingsEl.querySelector('.us-section.active h2');if(heading){heading.tabIndex=-1;heading.focus();}
|
|
331
234
|
}
|
|
332
235
|
|
|
333
236
|
function stopProp(e) {
|
|
334
237
|
e.stopPropagation();
|
|
335
238
|
}
|
|
336
239
|
|
|
337
|
-
// Project-only Mate DM gate. The persisted server field retains its legacy
|
|
338
|
-
// name for migration compatibility, but it no longer disables Mate features.
|
|
339
|
-
function applyProjectMateDmsClass(enabled) {
|
|
340
|
-
if (!document.body) return;
|
|
341
|
-
document.body.classList.toggle('project-mate-dms-disabled', !enabled);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
240
|
export function isProjectMateDmsEnabled() {
|
|
345
241
|
// Default to true until the profile arrives so boot does not briefly hide
|
|
346
242
|
// a project shortcut that the persisted preference enables.
|
|
347
|
-
return
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function showPinForm() {
|
|
351
|
-
if (!pinForm) return;
|
|
352
|
-
pinForm.classList.remove('hidden');
|
|
353
|
-
var pinCurrentRow = document.getElementById('us-pin-current-row');
|
|
354
|
-
var pinInput = document.getElementById('us-pin-input');
|
|
355
|
-
var pinConfirmInput = document.getElementById('us-pin-confirm-input');
|
|
356
|
-
var pinCurrentInput = document.getElementById('us-pin-current-input');
|
|
357
|
-
var pinSave = document.getElementById('us-pin-save');
|
|
358
|
-
var pinMsg = document.getElementById('us-pin-msg');
|
|
359
|
-
var pinFormTitle = document.getElementById('us-pin-form-title');
|
|
360
|
-
if (pinFormTitle) {
|
|
361
|
-
pinFormTitle.textContent = pinEnabled ? 'Change PIN' : 'Set PIN';
|
|
362
|
-
}
|
|
363
|
-
if (pinCurrentRow) {
|
|
364
|
-
pinCurrentRow.classList.toggle('hidden', !pinEnabled);
|
|
365
|
-
}
|
|
366
|
-
if (pinInput) {
|
|
367
|
-
pinInput.value = '';
|
|
368
|
-
}
|
|
369
|
-
if (pinConfirmInput) pinConfirmInput.value = '';
|
|
370
|
-
if (pinCurrentInput) pinCurrentInput.value = '';
|
|
371
|
-
if (pinSave) pinSave.disabled = true;
|
|
372
|
-
if (pinSave) pinSave.textContent = 'Save';
|
|
373
|
-
if (pinMsg) {
|
|
374
|
-
pinMsg.textContent = '';
|
|
375
|
-
pinMsg.className = 'settings-hint settings-pin-error hidden';
|
|
376
|
-
}
|
|
377
|
-
if (pinEnabled && pinCurrentInput) {
|
|
378
|
-
pinCurrentInput.focus();
|
|
379
|
-
} else if (pinInput) {
|
|
380
|
-
pinInput.focus();
|
|
381
|
-
}
|
|
243
|
+
return true;
|
|
382
244
|
}
|
|
383
245
|
|
|
384
|
-
function hidePinForm() {
|
|
385
|
-
if (!pinForm) return;
|
|
386
|
-
pinForm.classList.add('hidden');
|
|
387
|
-
var pinCurrentRow = document.getElementById('us-pin-current-row');
|
|
388
|
-
var pinInput = document.getElementById('us-pin-input');
|
|
389
|
-
var pinConfirmInput = document.getElementById('us-pin-confirm-input');
|
|
390
|
-
var pinCurrentInput = document.getElementById('us-pin-current-input');
|
|
391
|
-
var pinSave = document.getElementById('us-pin-save');
|
|
392
|
-
var pinMsg = document.getElementById('us-pin-msg');
|
|
393
|
-
if (pinCurrentRow) {
|
|
394
|
-
pinCurrentRow.classList.remove('hidden');
|
|
395
|
-
}
|
|
396
|
-
if (pinInput) pinInput.value = '';
|
|
397
|
-
if (pinConfirmInput) pinConfirmInput.value = '';
|
|
398
|
-
if (pinCurrentInput) pinCurrentInput.value = '';
|
|
399
|
-
if (pinSave) pinSave.disabled = true;
|
|
400
|
-
if (pinSave) pinSave.textContent = 'Save';
|
|
401
|
-
if (pinMsg) {
|
|
402
|
-
pinMsg.textContent = '';
|
|
403
|
-
pinMsg.className = 'settings-hint settings-pin-error hidden';
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// --- Account population ---
|
|
408
246
|
|
|
409
|
-
function
|
|
410
|
-
fetch('/api/profile').then(function (r) {
|
|
411
|
-
if (!r.ok) return null;
|
|
412
|
-
return r.json();
|
|
413
|
-
}).then(function (data) {
|
|
414
|
-
if (!data) return;
|
|
415
|
-
currentProfile = data;
|
|
416
|
-
var displayName = data.name || data.displayName || data.username || 'Clay User';
|
|
417
|
-
var avatarImg = document.getElementById('us-account-avatar-img');
|
|
418
|
-
var avatarFallback = document.getElementById('us-account-avatar-fallback');
|
|
419
|
-
var accountName = document.getElementById('us-account-name');
|
|
420
|
-
var accountSubline = document.getElementById('us-account-subline');
|
|
421
|
-
var langSelect = document.getElementById('us-lang-select');
|
|
422
|
-
var pinSetBtnEl = document.getElementById('us-pin-set-btn');
|
|
423
|
-
var pinFormTitle = document.getElementById('us-pin-form-title');
|
|
424
|
-
if (accountName) {
|
|
425
|
-
accountName.textContent = displayName;
|
|
426
|
-
}
|
|
427
|
-
if (accountSubline) {
|
|
428
|
-
var parts = [];
|
|
429
|
-
if (data.username) parts.push('@' + data.username);
|
|
430
|
-
if (data.role) parts.push(data.role.charAt(0).toUpperCase() + data.role.slice(1));
|
|
431
|
-
accountSubline.textContent = parts.length ? parts.join(' · ') : 'Local profile';
|
|
432
|
-
}
|
|
433
|
-
pinEnabled = !!data.pinEnabled;
|
|
434
|
-
if (pinSetBtnEl) {
|
|
435
|
-
pinSetBtnEl.textContent = pinEnabled ? 'Change PIN' : 'Set PIN';
|
|
436
|
-
}
|
|
437
|
-
if (pinFormTitle) {
|
|
438
|
-
pinFormTitle.textContent = pinEnabled ? 'Change PIN' : 'Set PIN';
|
|
439
|
-
}
|
|
440
|
-
if (langSelect) {
|
|
441
|
-
langSelect.value = store.get('speechLang') || 'en-US';
|
|
442
|
-
}
|
|
443
|
-
if (avatarImg && avatarFallback) {
|
|
444
|
-
var avatarUrl = getCurrentUserAvatarSrc(data);
|
|
445
|
-
avatarImg.alt = displayName;
|
|
446
|
-
avatarImg.classList.remove('hidden');
|
|
447
|
-
avatarFallback.classList.remove('hidden');
|
|
448
|
-
avatarFallback.textContent = avatarInitials(displayName);
|
|
449
|
-
avatarImg.onload = function () {
|
|
450
|
-
avatarFallback.classList.add('hidden');
|
|
451
|
-
};
|
|
452
|
-
avatarImg.onerror = function () {
|
|
453
|
-
avatarFallback.classList.remove('hidden');
|
|
454
|
-
};
|
|
455
|
-
avatarImg.src = avatarUrl;
|
|
456
|
-
if (avatarImg.complete && avatarImg.naturalWidth > 0) {
|
|
457
|
-
avatarFallback.classList.add('hidden');
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
// Hide account section in single-user mode (no username)
|
|
461
|
-
var accountNav = settingsEl.querySelector('[data-section="us-account"]');
|
|
462
|
-
if (accountNav) accountNav.style.display = data.username ? '' : 'none';
|
|
463
|
-
// Terminal font: seed in-memory prefs from server. Header-mounted
|
|
464
|
-
// controls (header-tui-font.js) listen to applyTerminalFont and
|
|
465
|
-
// sync their UI - we don't touch any DOM here.
|
|
466
|
-
if (data.terminalFont && typeof data.terminalFont === "object") {
|
|
467
|
-
applyTerminalFont(data.terminalFont.family, data.terminalFont.size);
|
|
468
|
-
}
|
|
469
|
-
// Auto-continue toggle
|
|
470
|
-
var acToggle = document.getElementById('us-auto-continue');
|
|
471
|
-
if (acToggle) acToggle.checked = !!data.autoContinueOnRateLimit;
|
|
472
|
-
// Theme picker self-syncs active selection via theme.js's applyTheme
|
|
473
|
-
// change callbacks (updatePickerActive). Nothing to do here.
|
|
474
|
-
// Layout switcher: sync from server response
|
|
475
|
-
// Sync mate onboarding state from server
|
|
476
|
-
if (data.mateOnboardingShown) {
|
|
477
|
-
try { localStorage.setItem("clay-mate-onboarding-shown", "1"); } catch (e) {}
|
|
478
|
-
}
|
|
479
|
-
// Project Mate DM shortcuts remain opt-in for legacy profiles.
|
|
480
|
-
var matesOn = data.matesEnabled === true;
|
|
481
|
-
var matesToggle = document.getElementById('us-mates-enabled');
|
|
482
|
-
if (matesToggle) matesToggle.checked = matesOn;
|
|
483
|
-
store.set({ projectMateDmsEnabled: matesOn });
|
|
484
|
-
applyProjectMateDmsClass(matesOn);
|
|
485
|
-
syncCapsulePreference(data.capsulesEnabled === true);
|
|
486
|
-
if (data.chatLayout) {
|
|
487
|
-
setChatLayout(data.chatLayout); // update local cache + CSS
|
|
488
|
-
}
|
|
489
|
-
var lSwitcher = document.getElementById('us-layout-switcher');
|
|
490
|
-
if (lSwitcher) {
|
|
491
|
-
var currentLayout = getChatLayout();
|
|
492
|
-
var lBtns = lSwitcher.querySelectorAll('.layout-option');
|
|
493
|
-
for (var li = 0; li < lBtns.length; li++) {
|
|
494
|
-
lBtns[li].classList.toggle('selected', lBtns[li].dataset.layout === currentLayout);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}).catch(function () {});
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function saveProfileChange(updates) {
|
|
501
|
-
if (!currentProfile) return;
|
|
502
|
-
var nextProfile = {};
|
|
503
|
-
var payload = {};
|
|
504
|
-
var key;
|
|
505
|
-
for (key in currentProfile) {
|
|
506
|
-
if (Object.prototype.hasOwnProperty.call(currentProfile, key)) {
|
|
507
|
-
nextProfile[key] = currentProfile[key];
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
for (key in updates) {
|
|
511
|
-
if (Object.prototype.hasOwnProperty.call(updates, key)) {
|
|
512
|
-
nextProfile[key] = updates[key];
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
currentProfile = nextProfile;
|
|
516
|
-
payload.name = nextProfile.name;
|
|
517
|
-
payload.lang = nextProfile.lang;
|
|
518
|
-
payload.avatarColor = nextProfile.avatarColor;
|
|
519
|
-
payload.avatarStyle = nextProfile.avatarStyle;
|
|
520
|
-
payload.avatarSeed = nextProfile.avatarSeed;
|
|
521
|
-
payload.avatarCustom = nextProfile.avatarCustom;
|
|
522
|
-
fetch('/api/profile', {
|
|
523
|
-
method: 'PUT',
|
|
524
|
-
headers: { 'Content-Type': 'application/json' },
|
|
525
|
-
body: JSON.stringify(payload),
|
|
526
|
-
}).catch(function () {});
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function avatarInitials(value) {
|
|
530
|
-
var text = (value || '').trim();
|
|
531
|
-
if (!text) return '?';
|
|
532
|
-
var parts = text.split(/\s+/);
|
|
533
|
-
var first = parts[0].charAt(0);
|
|
534
|
-
var second = parts.length > 1 ? parts[1].charAt(0) : parts[0].charAt(1);
|
|
535
|
-
var out = first + (second || '');
|
|
536
|
-
return out.toUpperCase();
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function getCurrentUserAvatarSrc(data) {
|
|
540
|
-
var islandAvatar = document.querySelector('.user-island-avatar img');
|
|
541
|
-
if (islandAvatar && islandAvatar.src) return islandAvatar.src;
|
|
542
|
-
if (data && data.avatarCustom) return data.avatarCustom;
|
|
543
|
-
return userAvatarUrl(data, 96);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function savePin(pin) {
|
|
547
|
-
var pinInput = document.getElementById('us-pin-input');
|
|
548
|
-
var pinConfirmInput = document.getElementById('us-pin-confirm-input');
|
|
549
|
-
var pinCurrentInput = document.getElementById('us-pin-current-input');
|
|
550
|
-
var pinSave = document.getElementById('us-pin-save');
|
|
551
|
-
var pinMsg = document.getElementById('us-pin-msg');
|
|
552
|
-
var currentPin = pinCurrentInput ? pinCurrentInput.value.trim() : '';
|
|
553
|
-
var confirmPin = pinConfirmInput ? pinConfirmInput.value.trim() : '';
|
|
554
|
-
var newPin = pinInput ? pinInput.value.trim() : '';
|
|
555
|
-
|
|
556
|
-
if (!/^\d{6}$/.test(newPin)) {
|
|
557
|
-
if (pinMsg) {
|
|
558
|
-
pinMsg.textContent = 'PIN must be exactly 6 digits.';
|
|
559
|
-
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
560
|
-
pinMsg.classList.remove('hidden');
|
|
561
|
-
}
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
if (confirmPin !== newPin) {
|
|
565
|
-
if (pinMsg) {
|
|
566
|
-
pinMsg.textContent = 'PINs do not match.';
|
|
567
|
-
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
568
|
-
pinMsg.classList.remove('hidden');
|
|
569
|
-
}
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
if (pinEnabled && !/^\d{6}$/.test(currentPin)) {
|
|
573
|
-
if (pinMsg) {
|
|
574
|
-
pinMsg.textContent = 'Current PIN is required.';
|
|
575
|
-
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
576
|
-
pinMsg.classList.remove('hidden');
|
|
577
|
-
}
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
pinSave.disabled = true;
|
|
582
|
-
pinSave.textContent = 'Saving\u2026';
|
|
583
|
-
|
|
584
|
-
fetch('/api/user/pin', {
|
|
585
|
-
method: 'PUT',
|
|
586
|
-
headers: { 'Content-Type': 'application/json' },
|
|
587
|
-
body: JSON.stringify({ currentPin: currentPin, newPin: newPin }),
|
|
588
|
-
}).then(function (r) { return r.json(); }).then(function (data) {
|
|
589
|
-
if (data.ok) {
|
|
590
|
-
showToast('PIN changed');
|
|
591
|
-
hidePinForm();
|
|
592
|
-
} else {
|
|
593
|
-
pinSave.disabled = false;
|
|
594
|
-
pinSave.textContent = 'Save';
|
|
595
|
-
if (pinMsg) {
|
|
596
|
-
pinMsg.textContent = data.error || 'Could not change your PIN. Please try again.';
|
|
597
|
-
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
598
|
-
pinMsg.classList.remove('hidden');
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}).catch(function () {
|
|
602
|
-
pinSave.disabled = false;
|
|
603
|
-
pinSave.textContent = 'Save';
|
|
604
|
-
if (pinMsg) {
|
|
605
|
-
pinMsg.textContent = 'Connection lost. Check your network and try again.';
|
|
606
|
-
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
607
|
-
pinMsg.classList.remove('hidden');
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// --- Email settings ---
|
|
613
|
-
|
|
614
|
-
function renderEmailSettings() {
|
|
615
|
-
var listEl = document.getElementById('us-email-list');
|
|
616
|
-
if (!listEl) return;
|
|
617
|
-
listEl.innerHTML = '';
|
|
618
|
-
|
|
619
|
-
var accounts = getEmailAccountListCache();
|
|
620
|
-
if (!accounts || accounts.length === 0) {
|
|
621
|
-
var empty = document.createElement('div');
|
|
622
|
-
empty.className = 'settings-field';
|
|
623
|
-
empty.innerHTML = '<div class="us-email-empty">No email accounts connected yet.</div>';
|
|
624
|
-
listEl.appendChild(empty);
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
for (var i = 0; i < accounts.length; i++) {
|
|
629
|
-
var acc = accounts[i];
|
|
630
|
-
var row = document.createElement('div');
|
|
631
|
-
row.className = 'us-email-row';
|
|
632
|
-
row.innerHTML =
|
|
633
|
-
'<div class="us-email-icon">' + providerIcon(acc.provider) + '</div>' +
|
|
634
|
-
'<div class="us-email-info">' +
|
|
635
|
-
'<div class="us-email-addr">' + escHtml(acc.email) + '</div>' +
|
|
636
|
-
'<div class="us-email-provider">' + escHtml(acc.label || acc.provider || 'Custom') + '</div>' +
|
|
637
|
-
'</div>' +
|
|
638
|
-
'<button class="us-email-remove-btn" data-account-id="' + escHtml(acc.id) + '">Remove</button>';
|
|
639
|
-
|
|
640
|
-
var removeBtn = row.querySelector('button');
|
|
641
|
-
removeBtn.addEventListener('click', function () {
|
|
642
|
-
var accountId = this.getAttribute('data-account-id');
|
|
643
|
-
if (ctx && ctx.ws && ctx.connected) {
|
|
644
|
-
ctx.ws.send(JSON.stringify({ type: 'email_account_remove', accountId: accountId }));
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
listEl.appendChild(row);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
export function refreshEmailSettings() {
|
|
653
|
-
if (isUserSettingsOpen()) renderEmailSettings();
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
var PROVIDER_ICON_PATHS = {
|
|
657
|
-
gmail: '/icons/email/gmail.svg',
|
|
658
|
-
outlook: '/icons/email/outlook.svg',
|
|
659
|
-
yahoo: '/icons/email/yahoo.svg',
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
function providerIcon(provider) {
|
|
663
|
-
var src = PROVIDER_ICON_PATHS[provider];
|
|
664
|
-
if (src) {
|
|
665
|
-
return '<img src="' + src + '" class="us-email-provider-icon" alt="' + provider + '">';
|
|
666
|
-
}
|
|
667
|
-
return '<i data-lucide="mail" class="us-email-provider-icon-fallback"></i>';
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function escHtml(str) {
|
|
671
|
-
var d = document.createElement('div');
|
|
672
|
-
d.textContent = str;
|
|
673
|
-
return d.innerHTML;
|
|
674
|
-
}
|
|
247
|
+
export function refreshEmailSettings() { if (isUserSettingsOpen()) renderEmailSettings(); }
|