clay-server 4.0.0-beta.9 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-request-access.js +66 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +185 -6
- package/lib/public/app.js +63 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +92 -11
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-surface-boot.js +7 -3
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-activation.js +45 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +31 -123
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +135 -46
- package/lib/sdk-message-processor.js +36 -7
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +137 -22
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +41 -0
- package/lib/sessions.js +57 -10
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +6 -6
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
|
@@ -14,44 +14,13 @@
|
|
|
14
14
|
// An in-flight save is debounced so rapid drag reorders don't spam.
|
|
15
15
|
|
|
16
16
|
import { refreshIcons } from './icons.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{ id: "terminal-sidebar-btn", icon: "square-terminal", label: "Terminal", countId: "terminal-sidebar-count" },
|
|
25
|
-
{ id: "sticky-notes-sidebar-btn", icon: "sticky-note", label: "Sticky Notes", countId: "sticky-notes-sidebar-count" },
|
|
26
|
-
{ id: "scheduler-btn", icon: "calendar-clock", label: "Scheduled Tasks" },
|
|
27
|
-
{ id: "loop-tool-btn", icon: "repeat", label: "Loop" },
|
|
28
|
-
{ id: "git-sidebar-btn", icon: "git-branch", label: "Git", countId: "git-sidebar-count" },
|
|
29
|
-
{ id: "mcp-btn", icon: "cable", label: "MCP Servers", countId: "mcp-sidebar-count" },
|
|
30
|
-
{ id: "skills-btn", icon: "puzzle", label: "Skills" },
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
var MATE_TOOLS = [
|
|
34
|
-
{ id: "mate-memory-btn", icon: "brain", label: "Memory", countId: "mate-memory-count" },
|
|
35
|
-
{ id: "mate-knowledge-btn", icon: "book-open", label: "Knowledge", countId: "mate-knowledge-count" },
|
|
36
|
-
{ id: "mate-sticky-notes-btn", icon: "sticky-note", label: "Sticky Notes" },
|
|
37
|
-
{ id: "mate-scheduler-btn", icon: "calendar-clock", label: "Scheduled Tasks" },
|
|
38
|
-
{ id: "mate-debate-btn", icon: "mic", label: "Debate" },
|
|
39
|
-
{ id: "mate-mcp-btn", icon: "cable", label: "MCP Servers", countId: "mate-mcp-sidebar-count" },
|
|
40
|
-
{ id: "mate-skills-btn", icon: "puzzle", label: "Skills" },
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
var PALETTES = {
|
|
44
|
-
session: {
|
|
45
|
-
tools: SESSION_TOOLS,
|
|
46
|
-
activeContainerId: "session-actions",
|
|
47
|
-
hiddenSectionId: "session-actions-hidden",
|
|
48
|
-
},
|
|
49
|
-
mate: {
|
|
50
|
-
tools: MATE_TOOLS,
|
|
51
|
-
activeContainerId: "mate-sidebar-tools",
|
|
52
|
-
hiddenSectionId: "mate-sidebar-tools-hidden",
|
|
53
|
-
},
|
|
54
|
-
};
|
|
17
|
+
import { PALETTES, normalizeToolPreferences } from './tool-palette-order.js';
|
|
18
|
+
import {
|
|
19
|
+
enterToolPickMode,
|
|
20
|
+
exitToolPickMode,
|
|
21
|
+
isToolPickActive,
|
|
22
|
+
openPaletteContextMenu
|
|
23
|
+
} from './tool-palette-overlays.js';
|
|
55
24
|
|
|
56
25
|
var _saveTimers = {};
|
|
57
26
|
var _draggingEl = null;
|
|
@@ -86,7 +55,7 @@ export function initToolPalettes() {
|
|
|
86
55
|
// handler below, which would immediately tear down the pick-mode
|
|
87
56
|
// we just entered.
|
|
88
57
|
e.stopPropagation();
|
|
89
|
-
if (
|
|
58
|
+
if (isToolPickActive()) exitToolPickMode();
|
|
90
59
|
else enterToolPickMode();
|
|
91
60
|
});
|
|
92
61
|
}
|
|
@@ -303,152 +272,6 @@ function moveToActive(name, toolId) {
|
|
|
303
272
|
// attached in buildToolButton before any external module attaches
|
|
304
273
|
// handlers by ID. See the stopImmediatePropagation logic there.)
|
|
305
274
|
|
|
306
|
-
// --- Right-click context menu ---
|
|
307
|
-
|
|
308
|
-
var _openMenu = null;
|
|
309
|
-
|
|
310
|
-
function openPaletteContextMenu(x, y, items) {
|
|
311
|
-
closePaletteContextMenu();
|
|
312
|
-
var menu = document.createElement('div');
|
|
313
|
-
menu.className = 'tool-palette-ctx-menu';
|
|
314
|
-
for (var i = 0; i < items.length; i++) {
|
|
315
|
-
(function (item) {
|
|
316
|
-
var btn = document.createElement('button');
|
|
317
|
-
btn.type = 'button';
|
|
318
|
-
btn.className = 'tool-palette-ctx-item';
|
|
319
|
-
btn.textContent = item.label;
|
|
320
|
-
btn.addEventListener('click', function (e) {
|
|
321
|
-
e.stopPropagation();
|
|
322
|
-
closePaletteContextMenu();
|
|
323
|
-
item.action();
|
|
324
|
-
});
|
|
325
|
-
menu.appendChild(btn);
|
|
326
|
-
})(items[i]);
|
|
327
|
-
}
|
|
328
|
-
document.body.appendChild(menu);
|
|
329
|
-
// Clamp to viewport.
|
|
330
|
-
var rect = menu.getBoundingClientRect();
|
|
331
|
-
var px = x, py = y;
|
|
332
|
-
if (px + rect.width > window.innerWidth - 4) px = window.innerWidth - rect.width - 4;
|
|
333
|
-
if (py + rect.height > window.innerHeight - 4) py = window.innerHeight - rect.height - 4;
|
|
334
|
-
menu.style.left = px + 'px';
|
|
335
|
-
menu.style.top = py + 'px';
|
|
336
|
-
_openMenu = menu;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function closePaletteContextMenu() {
|
|
340
|
-
if (_openMenu && _openMenu.parentNode) _openMenu.parentNode.removeChild(_openMenu);
|
|
341
|
-
_openMenu = null;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
document.addEventListener('click', function () { closePaletteContextMenu(); });
|
|
345
|
-
document.addEventListener('keydown', function (e) {
|
|
346
|
-
if (e.key === 'Escape') closePaletteContextMenu();
|
|
347
|
-
});
|
|
348
|
-
window.addEventListener('blur', function () { closePaletteContextMenu(); });
|
|
349
|
-
window.addEventListener('resize', function () { closePaletteContextMenu(); });
|
|
350
|
-
|
|
351
|
-
// --- Hotkey overlay (Cmd/Ctrl + O) ---
|
|
352
|
-
//
|
|
353
|
-
// Press Cmd/Ctrl + O to reveal a hotkey badge on each visible palette
|
|
354
|
-
// tile. Then press the shown digit (1..9, 0) or letter (a..z) to
|
|
355
|
-
// activate that tool. Escape or clicking away dismisses. Inspired by
|
|
356
|
-
// Vimium / Superhuman-style "pick a tile with one keystroke" flows.
|
|
357
|
-
|
|
358
|
-
var _pickActive = false;
|
|
359
|
-
var HOTKEYS = (function () {
|
|
360
|
-
var k = [];
|
|
361
|
-
for (var i = 1; i <= 9; i++) k.push(String(i));
|
|
362
|
-
k.push('0');
|
|
363
|
-
for (var c = 97; c <= 122; c++) k.push(String.fromCharCode(c));
|
|
364
|
-
return k;
|
|
365
|
-
})();
|
|
366
|
-
|
|
367
|
-
function getVisiblePaletteName() {
|
|
368
|
-
// offsetParent is null when the element (or an ancestor) is hidden.
|
|
369
|
-
var mateActive = document.getElementById('mate-sidebar-tools');
|
|
370
|
-
if (mateActive && mateActive.offsetParent !== null) return 'mate';
|
|
371
|
-
var sessionActive = document.getElementById('session-actions');
|
|
372
|
-
if (sessionActive && sessionActive.offsetParent !== null) return 'session';
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function enterToolPickMode() {
|
|
377
|
-
if (_pickActive) return;
|
|
378
|
-
var name = getVisiblePaletteName();
|
|
379
|
-
if (!name) return;
|
|
380
|
-
var active = document.getElementById(PALETTES[name].activeContainerId);
|
|
381
|
-
if (!active) return;
|
|
382
|
-
var tiles = active.querySelectorAll('[data-tool-id]');
|
|
383
|
-
if (tiles.length === 0) return;
|
|
384
|
-
for (var i = 0; i < tiles.length && i < HOTKEYS.length; i++) {
|
|
385
|
-
var key = HOTKEYS[i];
|
|
386
|
-
tiles[i].dataset.hotkey = key;
|
|
387
|
-
var badge = document.createElement('span');
|
|
388
|
-
badge.className = 'tool-palette-hotkey-badge';
|
|
389
|
-
badge.textContent = key.toUpperCase();
|
|
390
|
-
tiles[i].appendChild(badge);
|
|
391
|
-
}
|
|
392
|
-
_pickActive = true;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function exitToolPickMode() {
|
|
396
|
-
if (!_pickActive) return;
|
|
397
|
-
_pickActive = false;
|
|
398
|
-
var badges = document.querySelectorAll('.tool-palette-hotkey-badge');
|
|
399
|
-
for (var i = 0; i < badges.length; i++) {
|
|
400
|
-
if (badges[i].parentNode) badges[i].parentNode.removeChild(badges[i]);
|
|
401
|
-
}
|
|
402
|
-
var marked = document.querySelectorAll('[data-hotkey]');
|
|
403
|
-
for (var j = 0; j < marked.length; j++) {
|
|
404
|
-
delete marked[j].dataset.hotkey;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Capture phase so we reliably preempt typing and browser shortcuts
|
|
409
|
-
// when focus is in an input.
|
|
410
|
-
document.addEventListener('keydown', function (e) {
|
|
411
|
-
// Pick mode: consume the next printable key.
|
|
412
|
-
if (_pickActive) {
|
|
413
|
-
if (e.key === 'Escape') {
|
|
414
|
-
e.preventDefault();
|
|
415
|
-
exitToolPickMode();
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
// Ignore modifier-only keys; react to single-character keys.
|
|
419
|
-
if (e.key.length !== 1) return;
|
|
420
|
-
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
421
|
-
var key = e.key.toLowerCase();
|
|
422
|
-
var tile = document.querySelector('[data-hotkey="' + key + '"]');
|
|
423
|
-
if (tile) {
|
|
424
|
-
e.preventDefault();
|
|
425
|
-
exitToolPickMode();
|
|
426
|
-
tile.click();
|
|
427
|
-
} else {
|
|
428
|
-
// Unknown key exits pick mode without triggering a tile.
|
|
429
|
-
exitToolPickMode();
|
|
430
|
-
}
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Enter pick mode on Cmd/Ctrl + O (letter O). Intercepted globally
|
|
435
|
-
// including inside text inputs — the browser's native Cmd+O is a
|
|
436
|
-
// file-picker dialog Clay never wants, so taking it over costs the
|
|
437
|
-
// user nothing and is especially useful when jumping to a tool from
|
|
438
|
-
// the session search box.
|
|
439
|
-
if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && e.key && e.key.toLowerCase() === 'o') {
|
|
440
|
-
if (!getVisiblePaletteName()) return;
|
|
441
|
-
e.preventDefault();
|
|
442
|
-
enterToolPickMode();
|
|
443
|
-
}
|
|
444
|
-
}, true);
|
|
445
|
-
|
|
446
|
-
// Dismiss the overlay on click-away, blur, or viewport changes so it
|
|
447
|
-
// never lingers invisibly.
|
|
448
|
-
document.addEventListener('click', function () { if (_pickActive) exitToolPickMode(); });
|
|
449
|
-
window.addEventListener('blur', function () { if (_pickActive) exitToolPickMode(); });
|
|
450
|
-
window.addEventListener('resize', function () { if (_pickActive) exitToolPickMode(); });
|
|
451
|
-
|
|
452
275
|
function getDragAfterElement(container, x, y) {
|
|
453
276
|
var tiles = Array.prototype.slice.call(
|
|
454
277
|
container.querySelectorAll('[data-tool-id]:not(.dragging)')
|
|
@@ -495,13 +318,17 @@ function applyPreferences(name, prefs) {
|
|
|
495
318
|
var hiddenGrid = hiddenSection ? hiddenSection.querySelector('.tool-palette-hidden-grid') : null;
|
|
496
319
|
if (!active || !hiddenGrid) return;
|
|
497
320
|
|
|
321
|
+
var normalized = normalizeToolPreferences(name, prefs);
|
|
322
|
+
|
|
498
323
|
var hiddenSet = {};
|
|
499
|
-
var hiddenList =
|
|
324
|
+
var hiddenList = normalized.hidden;
|
|
500
325
|
for (var i = 0; i < hiddenList.length; i++) hiddenSet[hiddenList[i]] = true;
|
|
501
326
|
|
|
502
|
-
var orderList =
|
|
327
|
+
var orderList = normalized.order;
|
|
503
328
|
// Any tools not in the stored order are appended in registry order so
|
|
504
|
-
// newly-added tools surface automatically for existing users.
|
|
329
|
+
// newly-added tools surface automatically for existing users. This is how
|
|
330
|
+
// a restored tool reaches a saved palette that predates it: no rewrite, no
|
|
331
|
+
// server state, just the normal new-registry append.
|
|
505
332
|
var placed = {};
|
|
506
333
|
for (var j = 0; j < orderList.length; j++) {
|
|
507
334
|
var id = orderList[j];
|
|
@@ -525,6 +352,12 @@ function applyPreferences(name, prefs) {
|
|
|
525
352
|
hiddenGrid.appendChild(hbtn);
|
|
526
353
|
}
|
|
527
354
|
}
|
|
355
|
+
|
|
356
|
+
// Write the rewritten preference back once, through the normal save path, so
|
|
357
|
+
// the migration survives reloads and reaches the user's other devices. The
|
|
358
|
+
// save reads the DOM this function just arranged, so it records exactly what
|
|
359
|
+
// is on screen.
|
|
360
|
+
if (normalized.migrated) queueSave(name);
|
|
528
361
|
}
|
|
529
362
|
|
|
530
363
|
function queueSave(name) {
|
|
@@ -7,6 +7,7 @@ import { mateAvatarUrl } from './avatar.js';
|
|
|
7
7
|
import { getChatLayout } from './theme.js';
|
|
8
8
|
import { store } from './store.js';
|
|
9
9
|
import { VENDOR_NAMES } from './app-rendering.js';
|
|
10
|
+
import { startThinkingSegment, appendThinkingText, stopThinkingSegment, resetThinkingTurn, clearThinkingState, saveThinkingState, restoreThinkingState, updateThinkingTokenEstimate } from './thinking-lifecycle.js';
|
|
10
11
|
|
|
11
12
|
var ctx;
|
|
12
13
|
|
|
@@ -46,8 +47,6 @@ var todoMeta = {
|
|
|
46
47
|
|
|
47
48
|
// --- Tool tracking ---
|
|
48
49
|
var tools = {};
|
|
49
|
-
var currentThinking = null;
|
|
50
|
-
var thinkingGroup = null; // { el, count, totalDuration }
|
|
51
50
|
var pendingPermissions = {};
|
|
52
51
|
|
|
53
52
|
// --- Tool group tracking ---
|
|
@@ -203,6 +202,23 @@ export function toolActivityText(name, input) {
|
|
|
203
202
|
return "Running " + name + "...";
|
|
204
203
|
}
|
|
205
204
|
|
|
205
|
+
// The sublabel for a call that has finished. `toolActivityText` is written in
|
|
206
|
+
// the present progressive for a call in flight, so re-asserting it after a
|
|
207
|
+
// result has arrived leaves a finished card claiming to still be running —
|
|
208
|
+
// which is how a failed custom tool ended up reading "Running
|
|
209
|
+
// send_to_partner..." beside its own error output.
|
|
210
|
+
//
|
|
211
|
+
// Only ever used where a result exists, so a genuinely in-flight call is never
|
|
212
|
+
// relabelled. A failure states so plainly; a success keeps whatever specific
|
|
213
|
+
// description the activity text produced ("Reading foo.js"), because that still
|
|
214
|
+
// describes the call accurately, and only the generic present-progressive
|
|
215
|
+
// fallback is replaced.
|
|
216
|
+
export function toolTerminalText(name, input, isError) {
|
|
217
|
+
if (isError) return name + " failed";
|
|
218
|
+
var activity = toolActivityText(name, input);
|
|
219
|
+
return activity === "Running " + name + "..." ? name : activity;
|
|
220
|
+
}
|
|
221
|
+
|
|
206
222
|
function shortPath(p) {
|
|
207
223
|
if (!p) return "";
|
|
208
224
|
var parts = p.split("/");
|
|
@@ -1337,12 +1353,7 @@ export function markUserDialogResolved(requestId, behavior) {
|
|
|
1337
1353
|
// Annotates the active thinking block's label with a running token estimate.
|
|
1338
1354
|
// No-op when no thinking block is active.
|
|
1339
1355
|
export function updateThinkingTokens(estimatedTokens) {
|
|
1340
|
-
|
|
1341
|
-
var label = currentThinking.el.querySelector(".thinking-label");
|
|
1342
|
-
if (!label) return;
|
|
1343
|
-
var n = estimatedTokens || 0;
|
|
1344
|
-
var disp = n >= 1000 ? ("~" + (Math.round(n / 100) / 10) + "k") : ("~" + n);
|
|
1345
|
-
label.textContent = "Thinking " + disp + " tokens";
|
|
1356
|
+
updateThinkingTokenEstimate(estimatedTokens);
|
|
1346
1357
|
}
|
|
1347
1358
|
|
|
1348
1359
|
// --- Plan mode rendering ---
|
|
@@ -1712,128 +1723,23 @@ function updateTodoSticky() {
|
|
|
1712
1723
|
// --- Thinking ---
|
|
1713
1724
|
export function startThinking() {
|
|
1714
1725
|
ctx.finalizeAssistantBlock();
|
|
1715
|
-
|
|
1716
|
-
// Reuse existing thinking group if consecutive
|
|
1717
|
-
if (thinkingGroup && thinkingGroup.el.classList.contains("done")) {
|
|
1718
|
-
var el = thinkingGroup.el;
|
|
1719
|
-
el.classList.remove("done");
|
|
1720
|
-
el.querySelector(".thinking-content").textContent = "";
|
|
1721
|
-
var reuseLabel = el.querySelector(".thinking-label");
|
|
1722
|
-
if (reuseLabel) reuseLabel.textContent = "Thinking";
|
|
1723
|
-
// Mate mode: restore dots activity row, hide thinking header
|
|
1724
|
-
if (el.classList.contains("mate-thinking")) {
|
|
1725
|
-
var actRow = el.querySelector(".mate-thinking-activity");
|
|
1726
|
-
if (actRow) {
|
|
1727
|
-
actRow.style.display = "";
|
|
1728
|
-
}
|
|
1729
|
-
var header = el.querySelector(".thinking-header");
|
|
1730
|
-
if (header) header.style.display = "none";
|
|
1731
|
-
}
|
|
1732
|
-
currentThinking = { el: el, fullText: "", startTime: Date.now() };
|
|
1733
|
-
refreshIcons();
|
|
1734
|
-
maybeScrollToBottom();
|
|
1735
|
-
if (!el.classList.contains("mate-thinking")) {
|
|
1736
|
-
ctx.setActivity("thinking");
|
|
1737
|
-
}
|
|
1738
|
-
return;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
var el = document.createElement("div");
|
|
1742
|
-
el.className = "thinking-item";
|
|
1743
|
-
|
|
1744
|
-
if (ctx.isMateDm()) {
|
|
1745
|
-
var mateName = ctx.getMateName();
|
|
1746
|
-
var mateAvatar = ctx.getMateAvatarUrl();
|
|
1747
|
-
el.classList.add("mate-thinking");
|
|
1748
|
-
el.innerHTML =
|
|
1749
|
-
'<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
|
|
1750
|
-
'<div class="dm-bubble-content">' +
|
|
1751
|
-
'<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
|
|
1752
|
-
'<div class="mate-thinking-dots mate-thinking-activity"><span></span><span></span><span></span></div>' +
|
|
1753
|
-
'<div class="thinking-header" style="display:none">' +
|
|
1754
|
-
'<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
1755
|
-
'<span class="thinking-label">Thinking</span>' +
|
|
1756
|
-
'<span class="thinking-duration"></span>' +
|
|
1757
|
-
'<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
1758
|
-
'</div>' +
|
|
1759
|
-
'<div class="thinking-content"></div>' +
|
|
1760
|
-
'</div>';
|
|
1761
|
-
} else {
|
|
1762
|
-
el.innerHTML =
|
|
1763
|
-
'<div class="thinking-header">' +
|
|
1764
|
-
'<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
1765
|
-
'<span class="thinking-label">Thinking</span>' +
|
|
1766
|
-
'<span class="thinking-duration"></span>' +
|
|
1767
|
-
'<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
1768
|
-
'</div>' +
|
|
1769
|
-
'<div class="thinking-content"></div>';
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
el.querySelector(".thinking-header").addEventListener("click", function () {
|
|
1773
|
-
el.classList.toggle("expanded");
|
|
1774
|
-
});
|
|
1775
|
-
|
|
1776
|
-
ctx.addToMessages(el);
|
|
1777
|
-
refreshIcons();
|
|
1778
|
-
maybeScrollToBottom();
|
|
1779
|
-
thinkingGroup = { el: el, count: 0, totalDuration: 0 };
|
|
1780
|
-
currentThinking = { el: el, fullText: "", startTime: Date.now() };
|
|
1726
|
+
var el = startThinkingSegment();
|
|
1781
1727
|
if (!ctx.isMateDm()) {
|
|
1782
1728
|
ctx.setActivity("thinking");
|
|
1783
1729
|
}
|
|
1730
|
+
return el;
|
|
1784
1731
|
}
|
|
1785
1732
|
|
|
1786
1733
|
export function appendThinking(text) {
|
|
1787
|
-
|
|
1788
|
-
currentThinking.fullText += text;
|
|
1789
|
-
currentThinking.el.querySelector(".thinking-content").textContent = currentThinking.fullText;
|
|
1790
|
-
maybeScrollToBottom();
|
|
1734
|
+
appendThinkingText(text);
|
|
1791
1735
|
}
|
|
1792
1736
|
|
|
1793
1737
|
export function stopThinking(duration) {
|
|
1794
|
-
|
|
1795
|
-
var secs = typeof duration === "number" ? duration : (Date.now() - currentThinking.startTime) / 1000;
|
|
1796
|
-
currentThinking.el.classList.add("done");
|
|
1797
|
-
if (thinkingGroup && thinkingGroup.el === currentThinking.el) {
|
|
1798
|
-
thinkingGroup.count++;
|
|
1799
|
-
thinkingGroup.totalDuration += secs;
|
|
1800
|
-
currentThinking.el.querySelector(".thinking-duration").textContent = " " + thinkingGroup.totalDuration.toFixed(1) + "s";
|
|
1801
|
-
} else {
|
|
1802
|
-
currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs.toFixed(1) + "s";
|
|
1803
|
-
}
|
|
1804
|
-
// If no thinking text was streamed (e.g. Codex reasoning items arrive
|
|
1805
|
-
// with encrypted/hidden content, or Claude without extended-thinking),
|
|
1806
|
-
// the expand affordance is misleading because there's nothing inside.
|
|
1807
|
-
// Strip the chevron and the click handler so the header reads as a
|
|
1808
|
-
// plain label.
|
|
1809
|
-
var hasContent = !!(currentThinking.fullText && currentThinking.fullText.length > 0);
|
|
1810
|
-
if (!hasContent) {
|
|
1811
|
-
currentThinking.el.classList.add("empty");
|
|
1812
|
-
var chev = currentThinking.el.querySelector(".thinking-chevron");
|
|
1813
|
-
if (chev) chev.style.display = "none";
|
|
1814
|
-
var hdr = currentThinking.el.querySelector(".thinking-header");
|
|
1815
|
-
if (hdr) {
|
|
1816
|
-
hdr.style.cursor = "default";
|
|
1817
|
-
// Replace click listener by cloning the node (cheapest way to strip listeners).
|
|
1818
|
-
var clone = hdr.cloneNode(true);
|
|
1819
|
-
hdr.parentNode.replaceChild(clone, hdr);
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
// In mate mode: hide sparkle activity, show compact thinking header.
|
|
1823
|
-
if (currentThinking.el.classList.contains("mate-thinking")) {
|
|
1824
|
-
var actRow = currentThinking.el.querySelector(".mate-thinking-activity");
|
|
1825
|
-
if (actRow) actRow.style.display = "none";
|
|
1826
|
-
var header = currentThinking.el.querySelector(".thinking-header");
|
|
1827
|
-
if (header) {
|
|
1828
|
-
header.style.display = "";
|
|
1829
|
-
header.style.cursor = hasContent ? "pointer" : "default";
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
currentThinking = null;
|
|
1738
|
+
stopThinkingSegment(duration);
|
|
1833
1739
|
}
|
|
1834
1740
|
|
|
1835
1741
|
export function resetThinkingGroup() {
|
|
1836
|
-
|
|
1742
|
+
resetThinkingTurn();
|
|
1837
1743
|
}
|
|
1838
1744
|
|
|
1839
1745
|
// --- Tool items ---
|
|
@@ -2136,9 +2042,12 @@ export function updateToolResult(id, content, isError, images) {
|
|
|
2136
2042
|
var tool = tools[id];
|
|
2137
2043
|
if (!tool) return;
|
|
2138
2044
|
|
|
2045
|
+
// Re-render the sublabel from the now-complete input (streaming can have
|
|
2046
|
+
// built it from a partial one), but in its terminal form: a result has
|
|
2047
|
+
// arrived, so this call is no longer running.
|
|
2139
2048
|
var subtitleText = tool.el.querySelector(".tool-subtitle-text");
|
|
2140
2049
|
if (subtitleText && tool.input) {
|
|
2141
|
-
subtitleText.textContent =
|
|
2050
|
+
subtitleText.textContent = toolTerminalText(tool.name, tool.input, isError);
|
|
2142
2051
|
}
|
|
2143
2052
|
|
|
2144
2053
|
var resultBlock = document.createElement("div");
|
|
@@ -2501,7 +2410,7 @@ export function getHiddenResultTools() { return HIDDEN_RESULT_TOOLS; }
|
|
|
2501
2410
|
export function saveToolState() {
|
|
2502
2411
|
return {
|
|
2503
2412
|
tools: tools,
|
|
2504
|
-
|
|
2413
|
+
thinkingState: saveThinkingState(),
|
|
2505
2414
|
todoWidgetEl: todoWidgetEl,
|
|
2506
2415
|
todoMeta: todoMeta,
|
|
2507
2416
|
inPlanMode: inPlanMode,
|
|
@@ -2516,7 +2425,7 @@ export function saveToolState() {
|
|
|
2516
2425
|
|
|
2517
2426
|
export function restoreToolState(saved) {
|
|
2518
2427
|
tools = saved.tools;
|
|
2519
|
-
|
|
2428
|
+
restoreThinkingState(saved.thinkingState);
|
|
2520
2429
|
todoWidgetEl = saved.todoWidgetEl;
|
|
2521
2430
|
todoMeta = saved.todoMeta || normalizeTodoMeta();
|
|
2522
2431
|
inPlanMode = saved.inPlanMode;
|
|
@@ -2533,8 +2442,7 @@ export function restoreToolState(saved) {
|
|
|
2533
2442
|
|
|
2534
2443
|
export function resetToolState() {
|
|
2535
2444
|
tools = {};
|
|
2536
|
-
|
|
2537
|
-
thinkingGroup = null;
|
|
2445
|
+
clearThinkingState();
|
|
2538
2446
|
inPlanMode = false;
|
|
2539
2447
|
planContent = null;
|
|
2540
2448
|
currentPlanCardEl = null;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Snooze control for the update-available banner.
|
|
2
|
+
//
|
|
3
|
+
// Lives outside app-notifications.js so the banner module does not grow
|
|
4
|
+
// further, and because this is a self-contained concern: one compact menu, one
|
|
5
|
+
// message out, one message in.
|
|
6
|
+
//
|
|
7
|
+
// The client decides nothing. It sends an allowlisted duration key and, as a
|
|
8
|
+
// hint only, the browser's UTC offset so "Tomorrow" lands on the user's
|
|
9
|
+
// morning rather than the server's. The deadline, the target version and the
|
|
10
|
+
// user identity are all resolved server-side, so a tampered payload can only
|
|
11
|
+
// ever produce a refusal or a shorter snooze.
|
|
12
|
+
|
|
13
|
+
import { getWs } from './ws-ref.js';
|
|
14
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
15
|
+
|
|
16
|
+
// Must match SNOOZE_OPTIONS in lib/update-snooze.js. The server re-validates
|
|
17
|
+
// every key, so this list is presentation, not policy.
|
|
18
|
+
var SNOOZE_CHOICES = [
|
|
19
|
+
{ id: "3h", label: "3 hours" },
|
|
20
|
+
{ id: "8h", label: "8 hours" },
|
|
21
|
+
{ id: "tomorrow", label: "Tomorrow" },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
var openMenu = null;
|
|
25
|
+
var bannerRemover = null;
|
|
26
|
+
|
|
27
|
+
// app-notifications.js owns banner teardown (it runs the exit animation and
|
|
28
|
+
// keeps the clear-all button in sync). Registering the remover here keeps the
|
|
29
|
+
// dependency one-way, so the two modules never form a cycle.
|
|
30
|
+
export function setSnoozeBannerRemover(fn) {
|
|
31
|
+
bannerRemover = typeof fn === "function" ? fn : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isSnoozeChoice(id) {
|
|
35
|
+
for (var i = 0; i < SNOOZE_CHOICES.length; i++) {
|
|
36
|
+
if (SNOOZE_CHOICES[i].id === id) return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function closeMenu() {
|
|
42
|
+
if (!openMenu) return;
|
|
43
|
+
var trigger = openMenu.trigger;
|
|
44
|
+
if (openMenu.menu && openMenu.menu.parentNode) {
|
|
45
|
+
openMenu.menu.parentNode.removeChild(openMenu.menu);
|
|
46
|
+
}
|
|
47
|
+
openMenu = null;
|
|
48
|
+
if (trigger) trigger.setAttribute("aria-expanded", "false");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function sendSnooze(duration) {
|
|
52
|
+
if (!isSnoozeChoice(duration)) return;
|
|
53
|
+
var ws = getWs();
|
|
54
|
+
if (!ws || ws.readyState !== 1) return;
|
|
55
|
+
// A hint, not an instruction: the server validates the range, computes the
|
|
56
|
+
// instant itself, and clamps the result to its own maximum.
|
|
57
|
+
var offset = -new Date().getTimezoneOffset();
|
|
58
|
+
ws.send(JSON.stringify({ type: "update_snooze", duration: duration, tzOffsetMinutes: offset }));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildMenu(trigger) {
|
|
62
|
+
var menu = document.createElement("div");
|
|
63
|
+
menu.className = "notif-banner-snooze-menu";
|
|
64
|
+
menu.setAttribute("role", "menu");
|
|
65
|
+
menu.setAttribute("aria-label", "Snooze this update");
|
|
66
|
+
|
|
67
|
+
for (var i = 0; i < SNOOZE_CHOICES.length; i++) {
|
|
68
|
+
(function (choice) {
|
|
69
|
+
var item = document.createElement("button");
|
|
70
|
+
item.type = "button";
|
|
71
|
+
item.className = "notif-banner-snooze-item";
|
|
72
|
+
item.setAttribute("role", "menuitem");
|
|
73
|
+
item.textContent = choice.label;
|
|
74
|
+
item.addEventListener("click", function (e) {
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
closeMenu();
|
|
77
|
+
sendSnooze(choice.id);
|
|
78
|
+
});
|
|
79
|
+
menu.appendChild(item);
|
|
80
|
+
})(SNOOZE_CHOICES[i]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Roving focus with the arrow keys, Escape back to the trigger. Enough for a
|
|
84
|
+
// three-item menu to be usable without a pointer.
|
|
85
|
+
menu.addEventListener("keydown", function (e) {
|
|
86
|
+
var items = menu.querySelectorAll(".notif-banner-snooze-item");
|
|
87
|
+
var index = -1;
|
|
88
|
+
for (var i = 0; i < items.length; i++) {
|
|
89
|
+
if (items[i] === document.activeElement) { index = i; break; }
|
|
90
|
+
}
|
|
91
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
var next = e.key === "ArrowDown" ? index + 1 : index - 1;
|
|
94
|
+
if (next < 0) next = items.length - 1;
|
|
95
|
+
if (next >= items.length) next = 0;
|
|
96
|
+
if (items[next]) items[next].focus();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (e.key === "Escape") {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
e.stopPropagation();
|
|
102
|
+
closeMenu();
|
|
103
|
+
if (trigger) trigger.focus();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return menu;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Append a compact Snooze control to an existing update banner's action row.
|
|
111
|
+
// Reuses the banner's own button shape rather than introducing a new one.
|
|
112
|
+
export function attachSnoozeControl(actionsEl) {
|
|
113
|
+
if (!actionsEl) return null;
|
|
114
|
+
|
|
115
|
+
var wrap = document.createElement("div");
|
|
116
|
+
wrap.className = "notif-banner-snooze";
|
|
117
|
+
|
|
118
|
+
var trigger = document.createElement("button");
|
|
119
|
+
trigger.type = "button";
|
|
120
|
+
trigger.className = "notif-banner-snooze-btn";
|
|
121
|
+
trigger.setAttribute("aria-haspopup", "menu");
|
|
122
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
123
|
+
trigger.setAttribute("aria-label", "Snooze this update notification");
|
|
124
|
+
trigger.innerHTML = iconHtml("clock") + "<span>Snooze</span>";
|
|
125
|
+
|
|
126
|
+
trigger.addEventListener("click", function (e) {
|
|
127
|
+
e.stopPropagation();
|
|
128
|
+
if (openMenu && openMenu.trigger === trigger) {
|
|
129
|
+
closeMenu();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
closeMenu();
|
|
133
|
+
var menu = buildMenu(trigger);
|
|
134
|
+
wrap.appendChild(menu);
|
|
135
|
+
openMenu = { menu: menu, trigger: trigger };
|
|
136
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
137
|
+
var first = menu.querySelector(".notif-banner-snooze-item");
|
|
138
|
+
if (first) first.focus();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
wrap.appendChild(trigger);
|
|
142
|
+
actionsEl.appendChild(wrap);
|
|
143
|
+
refreshIcons();
|
|
144
|
+
return wrap;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// The server confirms a snooze to every device of that user, so each one drops
|
|
148
|
+
// the banner rather than only the device the user acted on.
|
|
149
|
+
export function handleUpdateSnoozed(msg) {
|
|
150
|
+
closeMenu();
|
|
151
|
+
if (!msg || !msg.ok) return;
|
|
152
|
+
var banner = document.querySelector('.notif-banner[data-notif-id="_update"]');
|
|
153
|
+
if (!banner) return;
|
|
154
|
+
if (msg.version && banner.getAttribute("data-update-version") !== msg.version) return;
|
|
155
|
+
if (bannerRemover) bannerRemover(banner);
|
|
156
|
+
else if (banner.parentNode) banner.parentNode.removeChild(banner);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Dismiss on click-away, blur, or a viewport change so the menu never lingers
|
|
160
|
+
// invisibly. Registered defensively because importing this module must stay
|
|
161
|
+
// side-effect-safe: several suites load the client graph against a partial
|
|
162
|
+
// document/window stub, and a dismiss convenience must not be what breaks them.
|
|
163
|
+
function onGlobal(target, name, fn) {
|
|
164
|
+
if (target && typeof target.addEventListener === "function") target.addEventListener(name, fn);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
onGlobal(typeof document !== "undefined" ? document : null, "click", function () { closeMenu(); });
|
|
168
|
+
onGlobal(typeof window !== "undefined" ? window : null, "blur", function () { closeMenu(); });
|
|
169
|
+
onGlobal(typeof window !== "undefined" ? window : null, "resize", function () { closeMenu(); });
|
|
@@ -10,6 +10,7 @@ import { setSTTLang } from './stt.js';
|
|
|
10
10
|
import { userAvatarUrl } from './avatar.js';
|
|
11
11
|
import { store } from './store.js';
|
|
12
12
|
import { getWs } from './ws-ref.js';
|
|
13
|
+
import { initCapsulePreference, syncCapsulePreference } from './capsule-preference.js';
|
|
13
14
|
|
|
14
15
|
var ctx = null;
|
|
15
16
|
var settingsEl = null;
|
|
@@ -34,6 +35,8 @@ export function initUserSettings(appCtx) {
|
|
|
34
35
|
|
|
35
36
|
if (!settingsEl || !openBtn) return;
|
|
36
37
|
|
|
38
|
+
initCapsulePreference();
|
|
39
|
+
|
|
37
40
|
navItems = settingsEl.querySelectorAll('.us-nav-item');
|
|
38
41
|
sections = settingsEl.querySelectorAll('.us-section');
|
|
39
42
|
pinForm = document.getElementById('us-pin-form');
|
|
@@ -466,6 +469,7 @@ function populateAccount() {
|
|
|
466
469
|
if (matesToggle) matesToggle.checked = matesOn;
|
|
467
470
|
store.set({ projectMateDmsEnabled: matesOn });
|
|
468
471
|
applyProjectMateDmsClass(matesOn);
|
|
472
|
+
syncCapsulePreference(data.capsulesEnabled === true);
|
|
469
473
|
if (data.chatLayout) {
|
|
470
474
|
setChatLayout(data.chatLayout); // update local cache + CSS
|
|
471
475
|
}
|