clay-server 5.5.0-beta.3 → 5.5.0-beta.5

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.
Files changed (108) hide show
  1. package/lib/github-connection-store.js +39 -0
  2. package/lib/github-runtime.js +43 -0
  3. package/lib/project-github-connection.js +164 -0
  4. package/lib/project.js +8 -0
  5. package/lib/public/appearance-bootstrap.js +6 -0
  6. package/lib/public/css/chrome-material.css +133 -0
  7. package/lib/public/css/menus.css +1 -17
  8. package/lib/public/css/navigation-island.css +261 -0
  9. package/lib/public/css/notifications-center.css +1 -17
  10. package/lib/public/css/settings-shell.css +0 -1
  11. package/lib/public/css/settings-ui.css +11 -2
  12. package/lib/public/css/shared-browser.css +1 -18
  13. package/lib/public/css/split-view-layout.css +9 -5
  14. package/lib/public/css/sticky-notes.css +1 -18
  15. package/lib/public/css/title-bar.css +1 -1
  16. package/lib/public/css/workspace-background-gallery.css +43 -0
  17. package/lib/public/css/you.css +13 -10
  18. package/lib/public/index.html +53 -46
  19. package/lib/public/modules/agent-behavior-nav.js +1 -0
  20. package/lib/public/modules/app-messages.js +5 -0
  21. package/lib/public/modules/appearance-preferences.js +81 -0
  22. package/lib/public/modules/context-sources.js +2 -282
  23. package/lib/public/modules/github-mark.js +2 -0
  24. package/lib/public/modules/pane-appearance-bridge.js +9 -1
  25. package/lib/public/modules/pane-header-tooltips.js +36 -9
  26. package/lib/public/modules/personal-email-editor.js +305 -0
  27. package/lib/public/modules/personal-integration-editor.js +70 -0
  28. package/lib/public/modules/personal-settings-dialog.js +15 -1
  29. package/lib/public/modules/session-list-material.js +25 -0
  30. package/lib/public/modules/settings-section-nav.js +2 -2
  31. package/lib/public/modules/settings-shell-registry.js +6 -5
  32. package/lib/public/modules/sidebar-collapse-focus.js +22 -0
  33. package/lib/public/modules/sidebar-sessions.js +2 -0
  34. package/lib/public/modules/sidebar.js +9 -10
  35. package/lib/public/modules/split-pane-renderer.js +5 -5
  36. package/lib/public/modules/split-view-shell-controls.js +9 -5
  37. package/lib/public/modules/split-view.js +2 -2
  38. package/lib/public/modules/theme.js +10 -28
  39. package/lib/public/modules/tool-pane-chrome.js +4 -1
  40. package/lib/public/modules/tool-pane-material.js +23 -0
  41. package/lib/public/modules/tooltip.js +7 -3
  42. package/lib/public/modules/tui-attention.js +9 -4
  43. package/lib/public/modules/user-settings-account.js +10 -1
  44. package/lib/public/modules/user-settings-github.js +176 -0
  45. package/lib/public/modules/user-settings-integrations.js +23 -12
  46. package/lib/public/modules/user-settings-layout.js +21 -18
  47. package/lib/public/modules/user-settings-themes.js +6 -2
  48. package/lib/public/modules/user-settings.js +2 -1
  49. package/lib/public/modules/workspace-background-gallery.js +92 -0
  50. package/lib/public/modules/workspace-custom-colors.js +132 -0
  51. package/lib/public/modules/you-activity.js +4 -2
  52. package/lib/public/pane-assets/app-C27ONMHZ.js +1 -0
  53. package/lib/public/pane-assets/chunk-4REWYEXI.js +1 -0
  54. package/lib/public/pane-assets/chunk-6EDR4PMU.js +1 -0
  55. package/lib/public/pane-assets/{chunk-4QFNLHW5.js → chunk-76JE6SO6.js} +1 -1
  56. package/lib/public/pane-assets/chunk-AFATDM32.js +1 -0
  57. package/lib/public/pane-assets/{chunk-P3AK4ORJ.js → chunk-DTNAWMGQ.js} +1 -1
  58. package/lib/public/pane-assets/chunk-EGCR2MH4.js +1 -0
  59. package/lib/public/pane-assets/chunk-ET4NUBYW.js +1 -0
  60. package/lib/public/pane-assets/chunk-EYJZ3R7Y.js +1 -0
  61. package/lib/public/pane-assets/{chunk-CXWAJEN5.js → chunk-I5Q4CZDJ.js} +1 -1
  62. package/lib/public/pane-assets/{chunk-JM46WGDH.js → chunk-KJN6KGEN.js} +1 -1
  63. package/lib/public/pane-assets/chunk-KZ3CYS5U.js +1 -0
  64. package/lib/public/pane-assets/chunk-MR4FHQFA.js +1 -0
  65. package/lib/public/pane-assets/chunk-NRC5RDIL.js +1 -0
  66. package/lib/public/pane-assets/chunk-NRP4NXVZ.js +1 -0
  67. package/lib/public/pane-assets/chunk-OERDYIHU.js +1 -0
  68. package/lib/public/pane-assets/{chunk-V5DLPSLT.js → chunk-OGIHAXLD.js} +1 -1
  69. package/lib/public/pane-assets/chunk-OQBKX36J.js +2 -0
  70. package/lib/public/pane-assets/chunk-Q2HJ5CWT.js +1 -0
  71. package/lib/public/pane-assets/{chunk-3EJ5IAHZ.js → chunk-QOL6PHMZ.js} +1 -1
  72. package/lib/public/pane-assets/{chunk-32YIYF7R.js → chunk-TVKTFEST.js} +1 -1
  73. package/lib/public/pane-assets/{chunk-XSSTR6GC.js → chunk-UMXZTYOY.js} +1 -1
  74. package/lib/public/pane-assets/chunk-W5MFUKXJ.js +1 -0
  75. package/lib/public/pane-assets/chunk-W7XXER5Z.js +174 -0
  76. package/lib/public/pane-assets/chunk-Y77DR2RU.js +1 -0
  77. package/lib/public/pane-assets/{chunk-XVESKMU3.js → chunk-YBYNU4VI.js} +1 -1
  78. package/lib/public/pane-assets/{chunk-JIV7O5GN.js → chunk-YHBTK56T.js} +1 -1
  79. package/lib/public/pane-assets/manifest.json +404 -390
  80. package/lib/public/pane-assets/style-F4MQRRX2.css +1 -0
  81. package/lib/public/style.css +4 -0
  82. package/lib/public/workspace-art/workshop-dark.webp +0 -0
  83. package/lib/public/workspace-art/workshop-light.webp +0 -0
  84. package/lib/public/workspace-background-catalog.js +40 -0
  85. package/lib/server-appearance-settings.js +16 -5
  86. package/lib/server.js +6 -0
  87. package/lib/session-github.js +1 -7
  88. package/lib/terminal-manager.js +5 -1
  89. package/lib/terminal.js +10 -1
  90. package/package.json +1 -1
  91. package/lib/public/pane-assets/app-ANJS2LSK.js +0 -1
  92. package/lib/public/pane-assets/chunk-3TIVBAKM.js +0 -1
  93. package/lib/public/pane-assets/chunk-5XMZMIFG.js +0 -1
  94. package/lib/public/pane-assets/chunk-6QFLYOVY.js +0 -1
  95. package/lib/public/pane-assets/chunk-6S2C7Q2N.js +0 -1
  96. package/lib/public/pane-assets/chunk-7B2GJI57.js +0 -1
  97. package/lib/public/pane-assets/chunk-BOFNCPTN.js +0 -1
  98. package/lib/public/pane-assets/chunk-EWTXBZJO.js +0 -2
  99. package/lib/public/pane-assets/chunk-G35ZOJGP.js +0 -1
  100. package/lib/public/pane-assets/chunk-HYT24UBT.js +0 -1
  101. package/lib/public/pane-assets/chunk-ISQEPZMY.js +0 -1
  102. package/lib/public/pane-assets/chunk-LBJG4MK2.js +0 -174
  103. package/lib/public/pane-assets/chunk-QKPGE7DN.js +0 -1
  104. package/lib/public/pane-assets/chunk-TS5JUEVH.js +0 -1
  105. package/lib/public/pane-assets/chunk-UGH5MCNK.js +0 -1
  106. package/lib/public/pane-assets/chunk-VFKDU64W.js +0 -1
  107. package/lib/public/pane-assets/chunk-VZ2OKDAO.js +0 -1
  108. package/lib/public/pane-assets/style-VBVCWV44.css +0 -1
@@ -1,24 +1,50 @@
1
- // Pane chrome uses the existing JS tooltip service, including lazy workbenches.
1
+ // Pane chrome and Worker viewers share the existing delegated tooltip service.
2
2
  import { registerTooltip, pruneTooltip } from './tooltip.js';
3
3
 
4
- var HEADERS = '.split-pane-header, .tool-pane-header, .file-viewer-tabbar, .terminal-header, .shared-browser-header, .split-view-workers, #top-bar .top-bar-actions';
4
+ var HEADERS = '.split-pane-header, .tool-pane-header, .file-viewer-tabbar, .terminal-header, .shared-browser-header, .split-view-workers, #top-bar .top-bar-actions, #split-view-worker-popover, body.worker-pane-locked #messages, body.worker-pane-locked #input-area';
5
+ var labels = new WeakMap();
5
6
  function sync(header) {
6
- header.querySelectorAll('[title], button, [role="button"], a[aria-label]').forEach(function (element) {
7
- var label = element.getAttribute('title') || element.getAttribute('data-tip') || element.getAttribute('aria-label');
8
- if (!label || !label.trim()) return;
9
- if (element.matches('button, [role="button"], a') && (!element.hasAttribute('aria-label') || element.dataset.tooltipGeneratedLabel === 'true' && element.hasAttribute('title'))) {
7
+ if (header.id === 'split-view-worker-popover' || header.id === 'messages') header.querySelectorAll('.tool-header[role="button"], .tool-group-header[role="button"]').forEach(function (element) {
8
+ if (element.hasAttribute('aria-label') && element.dataset.tooltipDisclosure !== 'true') return;
9
+ var name = element.querySelector('.tool-name, .tool-group-label');
10
+ if (!name) return;
11
+ element.dataset.tooltipDisclosure = 'true';
12
+ var label = (element.getAttribute('aria-expanded') === 'true' ? 'Hide ' : 'Show ') + name.textContent + ' details';
13
+ if (element.getAttribute('aria-label') !== label) element.setAttribute('aria-label', label);
14
+ });
15
+ // Move icon-owned native hints to the operable control before removing them.
16
+ header.querySelectorAll('[title], svg title').forEach(function (element) {
17
+ var control = element.closest('button, [role="button"], a, [role="img"]');
18
+ if (!control && element.matches('svg title')) control = element.parentElement;
19
+ if (!control || control === element) return;
20
+ var label = element.getAttribute('title') || element.textContent;
21
+ if (label && !control.hasAttribute('title') && !control.hasAttribute('data-tip') && !control.hasAttribute('aria-label')) control.setAttribute('title', label);
22
+ if (element.matches('svg title')) element.remove(); else element.removeAttribute('title');
23
+ });
24
+ (header.matches('[title], [data-tip]') ? [header] : []).concat(Array.from(header.querySelectorAll('[title], button, [role="button"], a[aria-label], [data-tip]'))).forEach(function (element) {
25
+ var previous = labels.get(element), tip = element.getAttribute('data-tip'), title = element.getAttribute('title');
26
+ var explicit = tip && (!previous || previous.explicit || tip !== previous.tip);
27
+ var aria = element.getAttribute('aria-label');
28
+ if (previous && aria && previous.aria !== aria) delete element.dataset.tooltipGeneratedLabel;
29
+ var label = explicit ? tip : title || (previous && previous.source === 'title' ? tip : aria || tip);
30
+ if (!label || !label.trim()) { element.removeAttribute('title'); return; }
31
+ if (element.matches('button, [role="button"], a') && (!element.hasAttribute('aria-label') || element.dataset.tooltipGeneratedLabel === 'true' && title)) {
10
32
  element.dataset.tooltipGeneratedLabel = 'true';
11
33
  if (element.getAttribute('aria-label') !== label) element.setAttribute('aria-label', label);
12
34
  }
13
- if (element.getAttribute('data-tip') !== label || element.hasAttribute('title')) registerTooltip(element, label);
35
+ if (tip !== label || element.hasAttribute('title')) registerTooltip(element, label);
36
+ labels.set(element, { tip: label, explicit: !!explicit, aria: element.getAttribute('aria-label'), source: title ? 'title' : previous && previous.source || 'aria' });
14
37
  });
15
38
  }
16
39
  export function initPaneHeaderTooltips() {
17
40
  document.querySelectorAll(HEADERS).forEach(sync);
18
41
  var observer = new MutationObserver(function (records) {
19
- pruneTooltip();
20
42
  var headers = new Set();
21
43
  records.forEach(function (record) {
44
+ if (record.type === 'attributes' && (record.attributeName === 'class' || record.attributeName === 'hidden')) {
45
+ if (record.target === document.body) document.querySelectorAll('body.worker-pane-locked #messages, body.worker-pane-locked #input-area').forEach(function (root) { headers.add(root); });
46
+ return;
47
+ }
22
48
  var parent = record.target.nodeType === 1 && record.target.closest(HEADERS); if (parent) headers.add(parent);
23
49
  (record.addedNodes || []).forEach(function (node) {
24
50
  if (node.nodeType !== 1) return;
@@ -27,6 +53,7 @@ export function initPaneHeaderTooltips() {
27
53
  });
28
54
  });
29
55
  headers.forEach(function (header) { if (header.isConnected) sync(header); });
56
+ pruneTooltip();
30
57
  });
31
- observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['title', 'aria-label'] });
58
+ observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['title', 'aria-label', 'data-tip', 'aria-expanded', 'hidden', 'class'] });
32
59
  }
@@ -0,0 +1,305 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { showToast } from './utils.js';
4
+ import { preparePersonalConnectionDialog, closePersonalEmailEditor } from './personal-settings-dialog.js';
5
+ import { selectIntegrationEditor } from './personal-integration-editor.js';
6
+
7
+ function settleEmailRequest() {
8
+ store.set({personalEmailSaving:false});
9
+ document.querySelectorAll('#email-setup-modal input, #email-setup-modal button').forEach(function (el) { el.disabled = false; });
10
+ }
11
+
12
+ export function showEmailSetupModal(provider, host) {
13
+ if (host) {
14
+ selectIntegrationEditor('email-add:' + provider, '#email-setup-modal', function () { openEmailSetup(provider, host); });
15
+ return;
16
+ }
17
+ openEmailSetup(provider, host);
18
+ }
19
+ function openEmailSetup(provider, host) {
20
+ var providerNames = { gmail: "Gmail", outlook: "Outlook Mail", yahoo: "Yahoo Mail", custom: "Custom email" };
21
+ if (!Object.hasOwn(providerNames, provider)) provider = null;
22
+ // Remove any existing modal
23
+ var existing = document.getElementById("email-setup-modal");
24
+ if (existing) {existing.querySelector("input:not([type=hidden]),button").focus();return;}
25
+
26
+ var overlay = document.createElement("div");
27
+ overlay.id = "email-setup-modal";
28
+ overlay.className = host ? "email-setup-inline" : "email-setup-overlay";
29
+
30
+ if (!provider) {
31
+ var picker = document.createElement('div'); picker.className = 'email-setup-dialog';
32
+ picker.innerHTML = '<h3>Choose an email provider</h3><div class="us-integration-tiles"></div><button id="email-setup-cancel" type="button" class="email-setup-btn">Cancel</button>';
33
+ Object.keys(providerNames).forEach(function (id) {
34
+ var button = document.createElement('button'); button.type = 'button'; button.className = 'email-setup-btn'; button.textContent = providerNames[id];
35
+ button.onclick = function () { closePicker(); showEmailSetupModal(id, host); };
36
+ picker.querySelector('.us-integration-tiles').appendChild(button);
37
+ });
38
+ overlay.appendChild(picker); (host || document.body).appendChild(overlay); var closePicker = preparePersonalConnectionDialog(overlay, !!host); return;
39
+ }
40
+ var modal = document.createElement("div");
41
+ modal.className = "email-setup-dialog";
42
+ modal.innerHTML =
43
+ '<h3 class="email-setup-title">Add ' + providerNames[provider] + ' account</h3>' +
44
+ '<input type="hidden" id="email-setup-provider" value="' + provider + '">' +
45
+ '<div class="email-setup-field">' +
46
+ '<label class="email-setup-label">Email Address</label>' +
47
+ '<input id="email-setup-address" type="email" placeholder="you@example.com" class="email-setup-input" />' +
48
+ '</div>' +
49
+ '<div class="email-setup-field">' +
50
+ '<label class="email-setup-label">App Password</label>' +
51
+ '<div class="email-setup-password-wrap">' +
52
+ '<input id="email-setup-password" type="password" placeholder="xxxx-xxxx-xxxx-xxxx" class="email-setup-input" />' +
53
+ '<button id="email-setup-password-toggle" type="button" class="email-setup-password-eye" title="Show password"><i data-lucide="eye"></i></button>' +
54
+ '</div>' +
55
+ '<a id="email-setup-help" href="https://support.google.com/accounts/answer/185833" target="_blank" rel="noopener" class="email-setup-help">How to create an App Password</a>' +
56
+ '</div>' +
57
+ '<div id="email-setup-custom-fields" style="display:none;">' +
58
+ '<div class="email-setup-field">' +
59
+ '<label class="email-setup-label">IMAP Host</label>' +
60
+ '<div class="email-setup-row">' +
61
+ '<input id="email-setup-imap-host" type="text" placeholder="imap.example.com" class="email-setup-input" style="flex:1;" />' +
62
+ '<input id="email-setup-imap-port" type="number" value="993" class="email-setup-input email-setup-port" />' +
63
+ '</div>' +
64
+ '</div>' +
65
+ '<div class="email-setup-field">' +
66
+ '<label class="email-setup-label">SMTP Host</label>' +
67
+ '<div class="email-setup-row">' +
68
+ '<input id="email-setup-smtp-host" type="text" placeholder="smtp.example.com" class="email-setup-input" style="flex:1;" />' +
69
+ '<input id="email-setup-smtp-port" type="number" value="587" class="email-setup-input email-setup-port" />' +
70
+ '</div>' +
71
+ '</div>' +
72
+ '</div>' +
73
+ '<div id="email-setup-status" class="email-setup-status"></div>' +
74
+ '<div class="email-setup-actions">' +
75
+ '<button id="email-setup-test" type="button" class="email-setup-btn email-setup-btn-secondary">Test Connection</button>' +
76
+ '<button id="email-setup-cancel" type="button" class="email-setup-btn email-setup-btn-ghost">Cancel</button>' +
77
+ '<button id="email-setup-save" type="button" class="email-setup-btn email-setup-btn-primary">Add Account</button>' +
78
+ '</div>';
79
+
80
+ overlay.appendChild(modal);
81
+ (host || document.body).appendChild(overlay);
82
+ preparePersonalConnectionDialog(overlay, !!host);
83
+
84
+ // Event: close on overlay click
85
+ overlay.addEventListener("click", function (e) {
86
+ if (!host && e.target === overlay) overlay.remove();
87
+ });
88
+
89
+ // Event: cancel button
90
+ document.getElementById("email-setup-cancel").addEventListener("click", function () {
91
+ overlay.remove();
92
+ });
93
+
94
+ // Event: password visibility toggle
95
+ var pwInput = document.getElementById("email-setup-password");
96
+ var pwToggle = document.getElementById("email-setup-password-toggle");
97
+ pwToggle.addEventListener("click", function () {
98
+ var isHidden = pwInput.type === "password";
99
+ pwInput.type = isHidden ? "text" : "password";
100
+ pwToggle.innerHTML = isHidden ? '<i data-lucide="eye-off"></i>' : '<i data-lucide="eye"></i>';
101
+ pwToggle.title = isHidden ? "Hide password" : "Show password";
102
+ if (typeof lucide !== "undefined") lucide.createIcons();
103
+ });
104
+
105
+ // Event: provider change
106
+ var providerSelect = document.getElementById("email-setup-provider");
107
+ var helpLink = document.getElementById("email-setup-help");
108
+ var customFields = document.getElementById("email-setup-custom-fields");
109
+
110
+ // Provider presets only configure IMAP/SMTP infra and the help link.
111
+ // The user always enters their full email address (e.g. user@company.com),
112
+ // so Google Workspace / Microsoft 365 accounts on custom domains work too.
113
+ var providerMeta = {
114
+ gmail: {
115
+ helpUrl: "https://support.google.com/accounts/answer/185833",
116
+ },
117
+ outlook: {
118
+ helpUrl: "https://support.microsoft.com/en-us/account-billing/using-app-passwords-with-apps-that-don-t-support-two-step-verification-5896ed9b-4263-e681-128a-a6f2979a7944",
119
+ },
120
+ yahoo: {
121
+ helpUrl: "https://help.yahoo.com/kb/generate-manage-third-party-passwords-sln15241.html",
122
+ },
123
+ custom: {
124
+ helpUrl: null,
125
+ },
126
+ };
127
+
128
+ var emailInput = document.getElementById("email-setup-address");
129
+
130
+ function applyProviderMeta(val) {
131
+ var meta = providerMeta[val] || providerMeta.custom;
132
+ customFields.style.display = val === "custom" ? "block" : "none";
133
+ if (meta.helpUrl) {
134
+ helpLink.href = meta.helpUrl;
135
+ helpLink.style.display = "";
136
+ } else {
137
+ helpLink.style.display = "none";
138
+ }
139
+ }
140
+
141
+ applyProviderMeta(provider);
142
+
143
+ // Reset test state
144
+ store.set({personalEmailTestPassed:false});
145
+ store.set({personalEmailPendingSave:false});
146
+
147
+ // Reset test status when inputs change
148
+ function onInputChange() {
149
+ store.set({personalEmailTestPassed:false});
150
+ store.set({personalEmailPendingSave:false});
151
+ var saveBtn = document.getElementById("email-setup-save");
152
+ if (saveBtn) saveBtn.textContent = "Add Account";
153
+ }
154
+ overlay.addEventListener('input', onInputChange);
155
+
156
+
157
+ function runTest() {
158
+ var statusEl = document.getElementById("email-setup-status");
159
+ var emailAddr = getFullEmail();
160
+ var appPass = document.getElementById("email-setup-password").value;
161
+
162
+ if (!emailAddr || emailAddr.indexOf("@") === -1 || !appPass) {
163
+ statusEl.style.display = "block";
164
+ statusEl.style.color = "var(--error, #e74c3c)";
165
+ statusEl.textContent = "Email and app password are required.";
166
+ store.set({personalEmailPendingSave:false});
167
+ return;
168
+ }
169
+
170
+ statusEl.style.display = "block";
171
+ statusEl.style.color = "var(--text-muted)";
172
+ statusEl.textContent = "Testing connection...";
173
+
174
+ var msgData = buildEmailSetupData();
175
+ msgData.type = "email_account_test";
176
+
177
+ var ws = getWs();
178
+ if (ws && ws.readyState === 1) {
179
+ store.set({personalEmailSaving:true,personalEmailRequestOwner:store.get('myUserId'),personalEmailRequestSocket:store.get('projectSplitViewSocketGeneration')});
180
+ document.querySelectorAll('#email-setup-modal input, #email-setup-modal button').forEach(function (el) { el.disabled = true; });
181
+ ws.send(JSON.stringify(msgData));
182
+ } else {
183
+ store.set({personalEmailPendingSave:false});
184
+ statusEl.textContent = 'Disconnected. Reconnect and retry.';
185
+ }
186
+ }
187
+
188
+ // Event: test connection
189
+ document.getElementById("email-setup-test").addEventListener("click", function () {
190
+ store.set({personalEmailPendingSave:false});
191
+ runTest();
192
+ });
193
+
194
+ // Event: save (requires test to pass first)
195
+ document.getElementById("email-setup-save").addEventListener("click", function () {
196
+ if (store.get('personalEmailTestPassed')) {
197
+ // Test already passed, proceed with save
198
+ doSave();
199
+ return;
200
+ }
201
+ // Run test first, then auto-save on success
202
+ store.set({personalEmailPendingSave:true});
203
+ var saveBtn = document.getElementById("email-setup-save");
204
+ if (saveBtn) saveBtn.textContent = "Testing...";
205
+ runTest();
206
+ });
207
+
208
+ }
209
+
210
+ function doSave() {
211
+ var statusEl = document.getElementById("email-setup-status");
212
+ statusEl.style.display = "block";
213
+ statusEl.style.color = "var(--text-muted)";
214
+ statusEl.textContent = "Adding account...";
215
+
216
+ var msgData = buildEmailSetupData();
217
+ msgData.type = "email_account_add";
218
+
219
+ var ws = getWs();
220
+ if (ws && ws.readyState === 1) {
221
+ store.set({personalEmailSaving:true,personalEmailRequestOwner:store.get('myUserId'),personalEmailRequestSocket:store.get('projectSplitViewSocketGeneration')});
222
+ document.querySelectorAll('#email-setup-modal input, #email-setup-modal button').forEach(function (el) { el.disabled = true; });
223
+ ws.send(JSON.stringify(msgData));
224
+ } else {
225
+ store.set({personalEmailPendingSave:false});
226
+ statusEl.textContent = 'Disconnected. Reconnect and retry.';
227
+ }
228
+ }
229
+
230
+ function getFullEmail() {
231
+ return document.getElementById("email-setup-address").value.trim();
232
+ }
233
+
234
+ function buildEmailSetupData() {
235
+ var provider = document.getElementById("email-setup-provider").value;
236
+ var email = getFullEmail();
237
+ var appPassword = document.getElementById("email-setup-password").value;
238
+ var data = { provider: provider, email: email, appPassword: appPassword };
239
+
240
+ if (provider === "custom") {
241
+ data.imap = {
242
+ host: document.getElementById("email-setup-imap-host").value.trim(),
243
+ port: parseInt(document.getElementById("email-setup-imap-port").value, 10) || 993,
244
+ tls: true,
245
+ };
246
+ data.smtp = {
247
+ host: document.getElementById("email-setup-smtp-host").value.trim(),
248
+ port: parseInt(document.getElementById("email-setup-smtp-port").value, 10) || 587,
249
+ };
250
+ }
251
+ return data;
252
+ }
253
+
254
+ // Handle email_account_test_result from server
255
+ export function handleEmailTestResult(msg) {
256
+ var statusEl = document.getElementById("email-setup-status");
257
+ if (!statusEl || !store.get('personalEmailSaving') || store.get('personalEmailRequestOwner') !== store.get('myUserId') || store.get('personalEmailRequestSocket') !== store.get('projectSplitViewSocketGeneration')) return;
258
+ var saveBtn = document.getElementById("email-setup-save");
259
+ settleEmailRequest();
260
+
261
+ statusEl.style.display = "block";
262
+ if (msg.ok) {
263
+ store.set({personalEmailTestPassed:true});
264
+ if (store.get('personalEmailPendingSave')) {
265
+ // Test passed from Add Account click, proceed to save
266
+ store.set({personalEmailPendingSave:false});
267
+ doSave();
268
+ return;
269
+ }
270
+ statusEl.style.color = "var(--success, #2ecc71)";
271
+ statusEl.textContent = "Connection successful! IMAP and SMTP both working.";
272
+ } else {
273
+ store.set({personalEmailTestPassed:false});
274
+ store.set({personalEmailPendingSave:false});
275
+ if (saveBtn) saveBtn.textContent = "Add Account";
276
+ statusEl.style.color = "var(--error, #e74c3c)";
277
+ var parts = [];
278
+ if (msg.imap && !msg.imap.ok) parts.push("IMAP: " + (msg.imap.error || "failed"));
279
+ if (msg.smtp && !msg.smtp.ok) parts.push("SMTP: " + (msg.smtp.error || "failed"));
280
+ statusEl.textContent = parts.length > 0 ? parts.join("; ") : (msg.error || "Connection failed");
281
+ }
282
+ }
283
+
284
+ // Handle email_account_add_result from server
285
+ export function handleEmailAddResult(msg) {
286
+ var statusEl = document.getElementById("email-setup-status");
287
+ if (!statusEl || !store.get('personalEmailSaving') || store.get('personalEmailRequestOwner') !== store.get('myUserId') || store.get('personalEmailRequestSocket') !== store.get('projectSplitViewSocketGeneration')) return;
288
+ settleEmailRequest();
289
+
290
+ statusEl.style.display = "block";
291
+ if (msg.ok) {
292
+ statusEl.style.color = "#2ecc71";
293
+ statusEl.textContent = "Account added successfully!";
294
+ closePersonalEmailEditor();
295
+ } else {
296
+ statusEl.style.color = "#e74c3c";
297
+ statusEl.textContent = msg.error || "Failed to add account.";
298
+ }
299
+ }
300
+
301
+ // Handle email_account_remove_result from server
302
+ export function handleEmailRemoveResult(msg) {
303
+ showToast(msg.ok ? 'Personal mailbox disconnected.' : msg.error || 'Could not disconnect mailbox. Retry from Email settings.');
304
+ // The acknowledged list arrives separately through email_accounts_list.
305
+ }
@@ -0,0 +1,70 @@
1
+ import { store } from './store.js';
2
+ import { confirmSettingsAction } from './server-settings-state.js';
3
+ import { showToast } from './utils.js';
4
+ import { closePersonalLinearEditor } from './user-settings-integrations.js';
5
+ import { closeGithubEditor, stopGithubEditorLogin } from './user-settings-github.js';
6
+ import { closePersonalEmailEditor } from './personal-settings-dialog.js';
7
+
8
+ export function integrationEditorBusy() {
9
+ return store.get('personalLinearSaving') || store.get('personalEmailSaving') || store.get('personalIntegrationSwitching') ||
10
+ store.get('githubSettingsPending') && store.get('githubSettingsAction') !== 'get' && store.get('githubSettingsAction') !== 'login';
11
+ }
12
+ function focusEditor(selector) {
13
+ var root = document.querySelector(selector);
14
+ if (!root) return;
15
+ var last = store.get('personalIntegrationFocus');
16
+ var target = last && root.contains(last) ? last : root.querySelector('input:not([type=hidden]),button') || root;
17
+ if (target.disabled) { root.tabIndex = -1; target = root; }
18
+ target.focus();
19
+ }
20
+ export function clearIntegrationEditor(key) {
21
+ var active = store.get('personalIntegrationEditor');
22
+ if (active && active.key === key) store.set({personalIntegrationEditor:null,personalIntegrationFocus:null});
23
+ }
24
+ function cleanup() {
25
+ var active = store.get('personalIntegrationEditor');
26
+ if (!active) return;
27
+ if (active.key === 'github') closeGithubEditor();
28
+ else if (active.key.indexOf('linear:') === 0) closePersonalLinearEditor();
29
+ else if (active.key.indexOf('email-add:') === 0) closePersonalEmailEditor();
30
+ else {
31
+ var panel = document.querySelector(active.selector);
32
+ if (panel) { panel.hidden = true; panel.parentElement.querySelector('[aria-expanded]').setAttribute('aria-expanded','false'); }
33
+ }
34
+ clearIntegrationEditor(active.key);
35
+ }
36
+ export function selectIntegrationEditor(key, selector, open) {
37
+ if (integrationEditorBusy()) {
38
+ var current = store.get('personalIntegrationEditor');
39
+ if (current) focusEditor(current.selector);
40
+ showToast('Wait for the current connection operation to finish.'); return;
41
+ }
42
+ var active = store.get('personalIntegrationEditor');
43
+ if (active && active.key === key) { focusEditor(selector); return; }
44
+ var actor = store.get('myUserId'), socket = store.get('projectSplitViewSocketGeneration');
45
+ async function proceed() {
46
+ if (actor !== store.get('myUserId') || socket !== store.get('projectSplitViewSocketGeneration') || integrationEditorBusy()) return;
47
+ store.set({personalIntegrationSwitching:true});
48
+ try {
49
+ if (active && active.key === 'github' && !await stopGithubEditorLogin()) return;
50
+ if (actor !== store.get('myUserId') || socket !== store.get('projectSplitViewSocketGeneration') || active !== store.get('personalIntegrationEditor')) return;
51
+ cleanup();
52
+ if (!key) { if (open) open(); return; }
53
+ store.set({personalIntegrationEditor:{key:key,selector:selector}});
54
+ open();
55
+ } finally { store.set({personalIntegrationSwitching:false}); }
56
+ }
57
+ if (active && (store.get('personalLinearDirty') || store.get('personalEmailDirty'))) {
58
+ focusEditor(active.selector);
59
+ confirmSettingsAction('Discard this unsaved connection draft?', 'Discard', proceed);
60
+ } else proceed();
61
+ }
62
+ export function bindIntegrationEditorFocus(root) {
63
+ root.addEventListener('focusin', function (event) {
64
+ var active = store.get('personalIntegrationEditor'), editor = active && document.querySelector(active.selector);
65
+ if (editor && editor.contains(event.target)) store.set({personalIntegrationFocus:event.target});
66
+ });
67
+ store.subscribe(function (state, previous) {
68
+ if (state.myUserId !== previous.myUserId) store.set({personalIntegrationEditor:null,personalIntegrationFocus:null});
69
+ });
70
+ }
@@ -1,3 +1,4 @@
1
+ import { selectIntegrationEditor, clearIntegrationEditor } from './personal-integration-editor.js';
1
2
  import { store } from './store.js';
2
3
  import { confirmSettingsAction, isolateSettingsDialog, trapSettingsFocus } from './server-settings-state.js';
3
4
  import { enhanceSettingsSelects } from './settings-select.js';
@@ -31,10 +32,18 @@ export function preparePersonalConnectionDialog(overlay, inline) {
31
32
  }) : function () {};
32
33
  function cleanup() { observer.disconnect(); unsubscribe(); restore(); if (inline) store.set({personalEmailDirty:false}); }
33
34
  function close() {
34
- cleanup();overlay.remove();
35
+ cleanup();
36
+ overlay.querySelectorAll('input,textarea').forEach(function (field) { field.value = ''; });
37
+ store.set({personalEmailSaving:false,personalEmailPendingSave:false,personalEmailTestPassed:false});
38
+ var active = store.get('personalIntegrationEditor');
39
+ if (inline && active && active.key.indexOf('email-add:') === 0) clearIntegrationEditor(active.key);
40
+ overlay.remove();
35
41
  if (previous && previous.isConnected) previous.focus();
36
42
  }
43
+ overlay.addEventListener('connection-close', close);
37
44
  function leave() {
45
+ if (store.get('personalEmailSaving')) return;
46
+ if (inline) { selectIntegrationEditor(null, null); return; }
38
47
  if (dirty) confirmSettingsAction('Discard this unsaved mailbox connection?', 'Discard', close);
39
48
  else close();
40
49
  }
@@ -55,3 +64,8 @@ export function preparePersonalConnectionDialog(overlay, inline) {
55
64
  (overlay.querySelector('#email-setup-address') || overlay.querySelector('button')).focus();
56
65
  return close;
57
66
  }
67
+
68
+ export function closePersonalEmailEditor() {
69
+ var overlay = document.getElementById('email-setup-modal');
70
+ if (overlay) overlay.dispatchEvent(new Event('connection-close'));
71
+ }
@@ -0,0 +1,25 @@
1
+ // Keep a clear sticky toolbar over the sidebar's one glass surface. Clip only
2
+ // the scrolling content's paint, without changing its layout or scroll owner.
3
+ export function bindSessionListMaterial(list) {
4
+ var scroller = list.parentElement;
5
+ function measure() {
6
+ var toolbar = list.querySelector(':scope > .session-list-sticky-top');
7
+ if (!toolbar) return;
8
+ var bottom = toolbar.getBoundingClientRect().bottom;
9
+ Array.from(list.children).forEach(function (content) {
10
+ if (content === toolbar) return;
11
+ var overlap = Math.max(0, bottom - content.getBoundingClientRect().top);
12
+ content.style.setProperty('--session-list-clip-top', overlap + 'px');
13
+ });
14
+ }
15
+ if (!scroller.dataset.sessionListMaterial) {
16
+ scroller.dataset.sessionListMaterial = 'true';
17
+ scroller.addEventListener('scroll', measure, { passive: true });
18
+ if (typeof ResizeObserver === 'function') {
19
+ var observer = new ResizeObserver(measure);
20
+ observer.observe(scroller); observer.observe(list);
21
+ }
22
+ else window.addEventListener('resize', measure);
23
+ }
24
+ measure();
25
+ }
@@ -41,11 +41,11 @@ export function activateSettingsSections(page, anchor) {
41
41
  link.className = 'ab-nav-link'; link.href = '#' + heading.id; link.dataset.section = heading.id;
42
42
  link.dataset.settingsAnchor = heading.id; link.textContent = heading.textContent; item.appendChild(link); list.appendChild(item);
43
43
  heading.tabIndex = -1;
44
- return { id: heading.id, el: heading, heading: heading };
44
+ return { id: heading.id, el: heading, heading: heading, disclosure: heading.parentElement.querySelector('details.us-experiments') };
45
45
  });
46
46
  layout.classList.add('ab-layout');
47
47
  var last = sections[sections.length - 1];
48
- if (last && last.el.nextElementSibling) last.el.nextElementSibling.style.minHeight = area.clientHeight + 'px';
48
+ if (last) last.el.closest('.settings-section-group').style.minHeight = area.clientHeight + 'px';
49
49
  var controller = attachSectionNav({ layout: layout, nav: nav, scroller: area, sections: sections, onPlace: function (narrow) {
50
50
  if (narrow) { setPageSubnav(null, null); layout.insertBefore(nav, layout.firstChild); }
51
51
  else setPageSubnav('us-providers', nav);
@@ -9,9 +9,10 @@ export var SETTINGS_SCOPES = [
9
9
  ];
10
10
 
11
11
  export var PERSONAL_PAGES = [
12
- { id: 'us-account', label: 'Account' }, { id: 'us-appearance', label: 'Appearance' },
13
- { id: 'us-providers', label: 'AI providers' }, { id: 'us-notifications', label: 'Notifications' },
14
- { id: 'us-ai', label: 'AI preferences' }, { id: 'us-agent', label: 'Agent behavior' }, { id: 'us-integrations', label: 'Integrations' }
12
+ { id: 'us-account', category: 'Personal', label: 'Account' }, { id: 'us-appearance', category: 'Personal', label: 'Appearance' },
13
+ { id: 'us-notifications', category: 'Personal', label: 'Notifications' },
14
+ { id: 'us-providers', category: 'AI', label: 'AI providers' }, { id: 'us-agent', category: 'AI', label: 'Agent behavior' },
15
+ { id: 'us-integrations', category: 'Connections', label: 'Integrations' }
15
16
  ];
16
17
 
17
18
  export var PROJECT_PAGES = [
@@ -31,7 +32,7 @@ export var SERVER_PAGES = [
31
32
  ];
32
33
 
33
34
  var PERSONAL_ALIASES = {
34
- 'us-chat': 'us-providers', 'us-mates': 'us-ai', 'us-experimental': 'us-ai', 'us-voice': 'us-providers', 'us-connections': 'us-integrations',
35
+ 'us-chat': 'us-providers', 'us-ai': 'us-providers', 'us-mates': 'us-providers', 'us-experimental': 'us-providers', 'us-voice': 'us-providers', 'us-connections': 'us-integrations',
35
36
  'us-email': 'us-integrations', 'us-logout': 'us-account'
36
37
  };
37
38
  var SERVER_ALIASES = { 'admin-invites': 'admin-users' };
@@ -83,7 +84,7 @@ export function resolveRoute(route, access) {
83
84
  scope: scope,
84
85
  page: resolvePage(scope, requested, access),
85
86
  slug: scope === 'project' ? (route.slug || null) : null,
86
- anchor: route.anchor || (route.page || '').split('#')[1] || (scope === 'personal' ? ({'us-chat':'api-connections','us-voice':'api-connections','us-experimental':'experimental-features','us-mates':'execution-behavior'})[route.page] : null) || null,
87
+ anchor: route.anchor || (route.page || '').split('#')[1] || (scope === 'personal' ? ({'us-chat':'api-connections','us-voice':'api-connections','us-experimental':'experimental-features','us-ai':'ai-defaults','us-mates':'execution-behavior'})[route.page] : null) || null,
87
88
  requestedScope: route.scope || 'personal',
88
89
  redirected: scope !== (route.scope || 'personal')
89
90
  };
@@ -0,0 +1,22 @@
1
+ // Keep desktop focus on the navigation controls when one side disappears.
2
+ export function restoreSidebarCollapseFocus(collapsed, previous) {
3
+ if (!previous || document.body.classList.contains('pane-mode') || !window.matchMedia('(min-width: 769px)').matches) return;
4
+ var leaving = collapsed ? previous.closest('#sidebar-column, #mate-sidebar-column') :
5
+ previous.closest('#sidebar-shell-expand-btn, #sidebar-expand-btn, .split-pane-sidebar');
6
+ if (!leaving) return;
7
+ function restore(attempt) {
8
+ var layout = document.getElementById('layout');
9
+ if (!layout || layout.classList.contains('sidebar-collapsed') !== collapsed ||
10
+ !window.matchMedia('(min-width: 769px)').matches ||
11
+ document.activeElement !== previous && document.activeElement !== document.body) return;
12
+ var mate = document.getElementById('mate-sidebar-column');
13
+ var sidebar = mate && !mate.classList.contains('hidden') ? mate : document.getElementById('sidebar-column');
14
+ var target = collapsed ? document.getElementById('sidebar-shell-expand-btn') :
15
+ sidebar && sidebar.querySelector('.sidebar-collapse-btn');
16
+ var region = collapsed ? document.getElementById('navigation-island') : sidebar;
17
+ if (target && !target.hidden && region && region.getBoundingClientRect().width > 0 && target.getClientRects().length) {
18
+ target.focus({ preventScroll: true });
19
+ } else if (attempt < 3) requestAnimationFrame(function () { restore(attempt + 1); });
20
+ }
21
+ restore(0);
22
+ }
@@ -7,6 +7,7 @@ import { escapeHtml } from './utils.js';
7
7
  import { iconHtml, refreshIcons } from './icons.js';
8
8
  import { openSearch as openSessionSearch } from './session-search.js';
9
9
  import { store } from './store.js';
10
+ import { bindSessionListMaterial } from './session-list-material.js';
10
11
  import { getWs } from './ws-ref.js';
11
12
  import { getSessionListEl } from './dom-refs.js';
12
13
  import { dismissOverlayPanels, closeSidebar, updatePageTitle, spawnDustParticles } from './sidebar.js';
@@ -1134,6 +1135,7 @@ export function renderSessionList(sessions) {
1134
1135
  regularContainer.appendChild(renderFavoritesSection(layout, folderCtx));
1135
1136
  renderOrdinarySections(layout, folderCtx, regularContainer);
1136
1137
  getSessionListEl().appendChild(regularContainer);
1138
+ bindSessionListMaterial(getSessionListEl());
1137
1139
  refreshIcons();
1138
1140
  reconcileSessionContextMenu();
1139
1141
  reconcileFolderMenu();