clay-server 2.36.0 → 2.36.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/public/app.js CHANGED
@@ -66,7 +66,7 @@ import { initAppNotifications, handleNotificationsState as _notifHandleState, ha
66
66
  import { createStore, store } from './modules/store.js';
67
67
  import { initPanels, updateConfigChip as _panUpdateConfigChip, getModelEffortLevels as _panGetModelEffortLevels, accumulateUsage as _panAccumulateUsage, updateUsagePanel as _panUpdateUsagePanel, resetUsage as _panResetUsage, toggleUsagePanel as _panToggleUsagePanel, formatTokens as _panFormatTokens, updateStatusPanel as _panUpdateStatusPanel, requestProcessStats as _panRequestProcessStats, toggleStatusPanel as _panToggleStatusPanel, accumulateContext as _panAccumulateContext, updateContextPanel as _panUpdateContextPanel, resetContext as _panResetContext, resetContextData as _panResetContextData, minimizeContext as _panMinimizeContext, expandContext as _panExpandContext, toggleContextPanel as _panToggleContextPanel, getContextView as _panGetContextView, renderCtxPopover as _panRenderCtxPopover, hideCtxPopover as _panHideCtxPopover, formatBytes as _panFormatBytes, formatUptime as _panFormatUptime, getModelSupportsEffort as _panGetModelSupportsEffort, getSessionUsage, setSessionUsage, getContextData, setContextData, setContextView as _panSetContextView, applyContextView as _panApplyContextView } from './modules/app-panels.js';
68
68
  import { initProjects, updateProjectList as _projUpdateProjectList, renderProjectList as _projRenderProjectList, renderTopbarPresence as _projRenderTopbarPresence, switchProject as _projSwitchProject, resetClientState as _projResetClientState, confirmRemoveProject as _projConfirmRemoveProject, handleRemoveProjectCheckResult as _projHandleRemoveProjectCheckResult, handleRemoveProjectResult as _projHandleRemoveProjectResult, openAddProjectModal as _projOpenAddProjectModal, closeAddProjectModal as _projCloseAddProjectModal, handleBrowseDirResult as _projHandleBrowseDirResult, handleAddProjectResult as _projHandleAddProjectResult, handleCloneProgress as _projHandleCloneProgress, showUpdateAvailable as _projShowUpdateAvailable, getCachedProjects, setCachedProjects, getCachedProjectCount, getCachedRemovedProjects, setCachedRemovedProjects } from './modules/app-projects.js';
69
- import { initRendering, addToMessages as _renAddToMessages, scrollToBottom as _renScrollToBottom, forceScrollToBottom as _renForceScrollToBottom, addUserMessage as _renAddUserMessage, getMsgTime as _renGetMsgTime, shouldGroupMessage as _renShouldGroupMessage, ensureAssistantBlock as _renEnsureAssistantBlock, addCopyHandler as _renAddCopyHandler, appendDelta as _renAppendDelta, flushStreamBuffer as _renFlushStreamBuffer, finalizeAssistantBlock as _renFinalizeAssistantBlock, addSystemMessage as _renAddSystemMessage, addConflictMessage as _renAddConflictMessage, addContextOverflowMessage as _renAddContextOverflowMessage, showClaudePreThinking as _renShowClaudePreThinking, showMatePreThinking as _renShowMatePreThinking, removeMatePreThinking as _renRemoveMatePreThinking, showSuggestionChips as _renShowSuggestionChips, hideSuggestionChips as _renHideSuggestionChips, getGhostSuggestion as _renGetGhostSuggestion, getTurnCounter, setTurnCounter, getPrependAnchor, setPrependAnchor, getActivityEl, setActivityEl, getIsUserScrolledUp, setIsUserScrolledUp } from './modules/app-rendering.js';
69
+ import { initRendering, addToMessages as _renAddToMessages, scrollToBottom as _renScrollToBottom, forceScrollToBottom as _renForceScrollToBottom, addUserMessage as _renAddUserMessage, getMsgTime as _renGetMsgTime, shouldGroupMessage as _renShouldGroupMessage, ensureAssistantBlock as _renEnsureAssistantBlock, addCopyHandler as _renAddCopyHandler, appendDelta as _renAppendDelta, flushStreamBuffer as _renFlushStreamBuffer, finalizeAssistantBlock as _renFinalizeAssistantBlock, addSystemMessage as _renAddSystemMessage, addConflictMessage as _renAddConflictMessage, addContextOverflowMessage as _renAddContextOverflowMessage, showClaudePreThinking as _renShowClaudePreThinking, showMatePreThinking as _renShowMatePreThinking, removeMatePreThinking as _renRemoveMatePreThinking, showSuggestionChips as _renShowSuggestionChips, hideSuggestionChips as _renHideSuggestionChips, getGhostSuggestion as _renGetGhostSuggestion, getTurnCounter, setTurnCounter, getPrependAnchor, setPrependAnchor, getActivityEl, setActivityEl, getIsUserScrolledUp, setIsUserScrolledUp, getStickyBottom, armStickyBottom, disarmStickyBottom } from './modules/app-rendering.js';
70
70
  import { initDm, openDm as _dmOpenDm, enterDmMode as _dmEnterDmMode, exitDmMode as _dmExitDmMode, handleMateCreatedInApp as _dmHandleMateCreatedInApp, renderAvailableBuiltins as _dmRenderAvailableBuiltins, buildMateInterviewPrompt as _dmBuildMateInterviewPrompt, updateMateIconStatus as _dmUpdateMateIconStatus, connectMateProject as _dmConnectMateProject, disconnectMateProject as _dmDisconnectMateProject, appendDmMessage as _dmAppendDmMessage, showDmTypingIndicator as _dmShowDmTypingIndicator, handleDmSend as _dmHandleDmSend } from './modules/app-dm.js';
71
71
  import { initMention, handleMentionStart, handleMentionStream, handleMentionDone, handleMentionError, handleMentionActivity, renderMentionUser, renderMentionResponse } from './modules/mention.js';
72
72
  import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState, exportDebateAsPdf, renderMcpDebateProposal } from './modules/debate.js';
@@ -518,6 +518,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
518
518
  var newMsgBtnActivity = "\u2193 New activity";
519
519
 
520
520
  messagesEl.addEventListener("scroll", function () {
521
+ // While sticky-bottom is armed (e.g. just after history_done or a "New
522
+ // activity" click), suppress "user scrolled up" detection. Growth-induced
523
+ // scroll events from deferred layout are not the user — the ResizeObserver
524
+ // is busy re-pinning to bottom. Real user input (wheel/touch/PageUp)
525
+ // disarms the flag separately, so this gate doesn't block genuine intent.
526
+ if (getStickyBottom()) return;
521
527
  var distFromBottom = messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight;
522
528
  var scrolledUp = distFromBottom > 150;
523
529
  setIsUserScrolledUp(scrolledUp);
@@ -38,7 +38,7 @@ import { handleMcpServersState } from './mcp-ui.js';
38
38
  import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, isSchedulerOpen, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './scheduler.js';
39
39
 
40
40
  // --- App module imports ---
41
- import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, showSuggestionChips } from './app-rendering.js';
41
+ import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, showSuggestionChips, armStickyBottom } from './app-rendering.js';
42
42
  import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot, updateCrossProjectBlink } from './app-favicon.js';
43
43
  import { setStatus } from './app-connection.js';
44
44
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
@@ -190,10 +190,29 @@ export function processMessage(msg) {
190
190
  var dbBanner = document.getElementById("debate-floor-banner");
191
191
  if (dbBanner) dbBanner.remove();
192
192
  }
193
- scrollToBottom();
194
- // Scroll to tool element if navigating from file edit history
193
+ // Resume landing position: arm sticky-bottom for ~1.5s so deferred
194
+ // layout (tool widgets via tools.js, markdown/syntax highlighting,
195
+ // image loads, IntersectionObserver-driven todo sticky reflows)
196
+ // can't strand the user mid-conversation. The ResizeObserver
197
+ // re-pins on every height change while armed. Disarms early on
198
+ // any real user scroll input.
199
+ // Skip arming when we have a pending in-conversation navigate
200
+ // target (file-edit deeplink) — the navigate block below scrolls
201
+ // that element into view, and sticky-bottom would fight it.
195
202
  var nav = getPendingNavigate();
196
- if (nav && (nav.toolId || nav.assistantUuid)) {
203
+ var hasNavTarget = nav && (nav.toolId || nav.assistantUuid);
204
+ if (hasNavTarget) {
205
+ // Navigate block below will scrollIntoView on the target — don't
206
+ // arm sticky-bottom or it would fight that scroll.
207
+ scrollToBottom();
208
+ } else {
209
+ // Quiet window: ResizeObserver extends this for as long as
210
+ // layout keeps shifting (long sessions, late-rendering tool
211
+ // widgets, image loads), bounded by an internal hard ceiling.
212
+ armStickyBottom(750);
213
+ }
214
+ // Scroll to tool element if navigating from file edit history
215
+ if (hasNavTarget) {
197
216
  requestAnimationFrame(function() {
198
217
  // Prefer scrolling to the exact tool element
199
218
  var target = nav.toolId ? messagesEl.querySelector('[data-tool-id="' + nav.toolId + '"]') : null;
@@ -36,6 +36,109 @@ var streamDrainTimer = null;
36
36
  var isUserScrolledUp = false;
37
37
  var scrollThreshold = 150;
38
38
 
39
+ // --- Sticky-bottom mode ---
40
+ // While armed, a ResizeObserver re-pins #messages to scrollHeight on every
41
+ // height change so deferred content (tools, syntax highlighting, images,
42
+ // IntersectionObserver-driven reflows) doesn't strand the user mid-page.
43
+ // The scroll listener in app.js consults getStickyBottom() and ignores
44
+ // growth-induced scroll events while armed.
45
+ //
46
+ // Disarm rules:
47
+ // - Real user input (wheel / touchmove / PageUp / Home / ArrowUp): immediate.
48
+ // - Quiet detector: armStickyBottom(durationMs) treats durationMs as the
49
+ // QUIET WINDOW, not a hard timer. Each ResizeObserver callback resets
50
+ // a debounce timer; sticky-bottom only disarms after no resize for
51
+ // durationMs. Long-settling sessions (large todo widgets, slow code
52
+ // highlighting) keep extending the window naturally.
53
+ // - Hard ceiling: a separate cap prevents pathological lock-in.
54
+ var stickyBottom = false;
55
+ var stickyBottomQuietTimer = null;
56
+ var stickyBottomCeilingTimer = null;
57
+ var stickyBottomQuietMs = 750;
58
+ var stickyBottomCeilingMs = 8000;
59
+ var stickyBottomResizeObs = null;
60
+ var stickyBottomInputBound = false;
61
+
62
+ export function getStickyBottom() { return stickyBottom; }
63
+
64
+ function pinToBottomNow() {
65
+ var messagesEl = getMessagesEl();
66
+ if (!messagesEl) return;
67
+ messagesEl.scrollTop = messagesEl.scrollHeight;
68
+ }
69
+
70
+ function ensureStickyInfrastructure() {
71
+ var messagesEl = getMessagesEl();
72
+ if (!messagesEl) return;
73
+ if (!stickyBottomResizeObs && typeof ResizeObserver !== "undefined") {
74
+ stickyBottomResizeObs = new ResizeObserver(function () {
75
+ if (!stickyBottom) return;
76
+ // Re-pin on every layout change while armed.
77
+ pinToBottomNow();
78
+ // Reset the quiet timer — settling has not finished yet.
79
+ if (stickyBottomQuietTimer) clearTimeout(stickyBottomQuietTimer);
80
+ stickyBottomQuietTimer = setTimeout(disarmStickyBottom, stickyBottomQuietMs);
81
+ });
82
+ stickyBottomResizeObs.observe(messagesEl);
83
+ // Also observe direct children so child-size changes (image loads, code
84
+ // block highlighting, expanding tool groups) trigger a re-pin even when
85
+ // they don't change the scroller's own size.
86
+ var kids = messagesEl.children;
87
+ for (var i = 0; i < kids.length; i++) stickyBottomResizeObs.observe(kids[i]);
88
+ }
89
+ if (!stickyBottomInputBound) {
90
+ stickyBottomInputBound = true;
91
+ var disarmOnUserScroll = function () { disarmStickyBottom(); };
92
+ messagesEl.addEventListener("wheel", disarmOnUserScroll, { passive: true });
93
+ messagesEl.addEventListener("touchmove", disarmOnUserScroll, { passive: true });
94
+ document.addEventListener("keydown", function (e) {
95
+ if (!stickyBottom) return;
96
+ if (e.key === "PageUp" || e.key === "Home" || e.key === "ArrowUp") {
97
+ disarmStickyBottom();
98
+ }
99
+ });
100
+ }
101
+ }
102
+
103
+ export function armStickyBottom(durationMs) {
104
+ if (prependAnchor) return; // never fight pagination
105
+ ensureStickyInfrastructure();
106
+ stickyBottom = true;
107
+ isUserScrolledUp = false;
108
+ var newMsgBtn = document.getElementById("new-msg-btn");
109
+ if (newMsgBtn) {
110
+ newMsgBtn.classList.add("hidden");
111
+ newMsgBtn.textContent = NEW_MSG_BTN_DEFAULT;
112
+ }
113
+ pinToBottomNow();
114
+ // After children may have been replaced since last arm, re-observe.
115
+ if (stickyBottomResizeObs) {
116
+ var messagesEl = getMessagesEl();
117
+ if (messagesEl) {
118
+ var kids = messagesEl.children;
119
+ for (var i = 0; i < kids.length; i++) {
120
+ try { stickyBottomResizeObs.observe(kids[i]); } catch (e) {}
121
+ }
122
+ }
123
+ }
124
+ // Quiet window: callers pass intended quiet duration; ResizeObserver
125
+ // resets this each time layout changes, so the actual armed duration
126
+ // stretches to "no resize for durationMs".
127
+ stickyBottomQuietMs = durationMs || 750;
128
+ if (stickyBottomQuietTimer) clearTimeout(stickyBottomQuietTimer);
129
+ stickyBottomQuietTimer = setTimeout(disarmStickyBottom, stickyBottomQuietMs);
130
+ // Hard ceiling so we never lock the scroller indefinitely if some
131
+ // animation/observer keeps firing forever.
132
+ if (stickyBottomCeilingTimer) clearTimeout(stickyBottomCeilingTimer);
133
+ stickyBottomCeilingTimer = setTimeout(disarmStickyBottom, stickyBottomCeilingMs);
134
+ }
135
+
136
+ export function disarmStickyBottom() {
137
+ stickyBottom = false;
138
+ if (stickyBottomQuietTimer) { clearTimeout(stickyBottomQuietTimer); stickyBottomQuietTimer = null; }
139
+ if (stickyBottomCeilingTimer) { clearTimeout(stickyBottomCeilingTimer); stickyBottomCeilingTimer = null; }
140
+ }
141
+
39
142
  export function initRendering() {
40
143
  // Update input placeholder when vendor changes
41
144
  store.subscribe(function (state, prev) {
@@ -86,14 +189,11 @@ export function scrollToBottom() {
86
189
 
87
190
  export function forceScrollToBottom() {
88
191
  if (prependAnchor) return;
89
- isUserScrolledUp = false;
90
- var newMsgBtn = document.getElementById("new-msg-btn");
91
- newMsgBtn.classList.add("hidden");
92
- newMsgBtn.textContent = NEW_MSG_BTN_DEFAULT;
93
- var messagesEl = getMessagesEl();
94
- requestAnimationFrame(function () {
95
- messagesEl.scrollTop = messagesEl.scrollHeight;
96
- });
192
+ // Arm sticky-bottom mode so deferred layout (tool widgets, code highlighting,
193
+ // image loads) can't strand the user partway down — single-rAF pin captures
194
+ // a stale scrollHeight, then growth below pushes the bottom further away.
195
+ // The quiet detector extends the window automatically while layout shifts.
196
+ armStickyBottom(750);
97
197
  }
98
198
 
99
199
  export function getMsgTime() {
@@ -10,6 +10,16 @@ import { VENDOR_NAMES } from './app-rendering.js';
10
10
 
11
11
  var ctx;
12
12
 
13
+ // During history replay, individual tool renders (todos, file edits, command
14
+ // outputs) must not auto-scroll. The history_done handler arms sticky-bottom
15
+ // which pins the viewport to the true bottom after the whole replay settles.
16
+ // Per-tool scroll calls during replay fight that and re-anchor the user to
17
+ // whichever tool widget grew last (commonly the todo widget).
18
+ function maybeScrollToBottom() {
19
+ if (store.get('replayingHistory')) return;
20
+ if (ctx && ctx.scrollToBottom) ctx.scrollToBottom();
21
+ }
22
+
13
23
  // --- Plan mode state ---
14
24
  var inPlanMode = false;
15
25
  var planContent = null;
@@ -365,7 +375,7 @@ export function renderAskUserQuestion(toolId, input) {
365
375
  ctx.addToMessages(container);
366
376
  disableMainInput();
367
377
  ctx.setActivity(null);
368
- ctx.scrollToBottom();
378
+ maybeScrollToBottom();
369
379
  }
370
380
 
371
381
  export function disableMainInput() {
@@ -602,7 +612,7 @@ function renderFormalPermission(requestId, toolName, toolInput, decisionReason)
602
612
  pendingPermissions[requestId] = container;
603
613
  refreshIcons();
604
614
  ctx.setActivity(null);
605
- ctx.scrollToBottom();
615
+ maybeScrollToBottom();
606
616
  }
607
617
 
608
618
  function renderPlanPermission(requestId) {
@@ -709,7 +719,7 @@ function renderPlanPermission(requestId) {
709
719
  pendingPermissions[requestId] = container;
710
720
  refreshIcons();
711
721
  ctx.setActivity(null);
712
- ctx.scrollToBottom();
722
+ maybeScrollToBottom();
713
723
  // Focus the feedback input after render
714
724
  setTimeout(function () { feedbackInput.focus(); }, 50);
715
725
  }
@@ -888,7 +898,7 @@ function renderConversationalPermission(requestId, toolName, toolInput, mateId,
888
898
  pendingPermissions[requestId] = container;
889
899
  refreshIcons();
890
900
  ctx.setActivity(null);
891
- ctx.scrollToBottom();
901
+ maybeScrollToBottom();
892
902
  }
893
903
 
894
904
  function sendPermissionResponse(container, requestId, decision) {
@@ -1115,7 +1125,7 @@ export function renderElicitationRequest(msg) {
1115
1125
  pendingElicitations[msg.requestId] = container;
1116
1126
  refreshIcons();
1117
1127
  ctx.setActivity(null);
1118
- ctx.scrollToBottom();
1128
+ maybeScrollToBottom();
1119
1129
  }
1120
1130
 
1121
1131
  function sendElicitationResponse(container, requestId, action, content) {
@@ -1194,7 +1204,7 @@ export function renderPlanBanner(type) {
1194
1204
 
1195
1205
  ctx.addToMessages(el);
1196
1206
  refreshIcons();
1197
- ctx.scrollToBottom();
1207
+ maybeScrollToBottom();
1198
1208
  return el;
1199
1209
  }
1200
1210
 
@@ -1255,7 +1265,7 @@ export function renderPlanCard(content) {
1255
1265
  }
1256
1266
 
1257
1267
  refreshIcons();
1258
- if (isNew) ctx.scrollToBottom();
1268
+ if (isNew) maybeScrollToBottom();
1259
1269
  return el;
1260
1270
  }
1261
1271
 
@@ -1388,7 +1398,7 @@ function renderTodoWidget() {
1388
1398
  }
1389
1399
  updateTodoSticky();
1390
1400
  refreshIcons();
1391
- ctx.scrollToBottom();
1401
+ maybeScrollToBottom();
1392
1402
  }
1393
1403
 
1394
1404
  function setupTodoObserver() {
@@ -1517,7 +1527,7 @@ export function startThinking() {
1517
1527
  }
1518
1528
  currentThinking = { el: el, fullText: "", startTime: Date.now() };
1519
1529
  refreshIcons();
1520
- ctx.scrollToBottom();
1530
+ maybeScrollToBottom();
1521
1531
  if (!el.classList.contains("mate-thinking")) {
1522
1532
  ctx.setActivity("thinking");
1523
1533
  }
@@ -1561,7 +1571,7 @@ export function startThinking() {
1561
1571
 
1562
1572
  ctx.addToMessages(el);
1563
1573
  refreshIcons();
1564
- ctx.scrollToBottom();
1574
+ maybeScrollToBottom();
1565
1575
  thinkingGroup = { el: el, count: 0, totalDuration: 0 };
1566
1576
  currentThinking = { el: el, fullText: "", startTime: Date.now() };
1567
1577
  if (!ctx.isMateDm()) {
@@ -1573,7 +1583,7 @@ export function appendThinking(text) {
1573
1583
  if (!currentThinking) return;
1574
1584
  currentThinking.fullText += text;
1575
1585
  currentThinking.el.querySelector(".thinking-content").textContent = currentThinking.fullText;
1576
- ctx.scrollToBottom();
1586
+ maybeScrollToBottom();
1577
1587
  }
1578
1588
 
1579
1589
  export function stopThinking(duration) {
@@ -1699,7 +1709,7 @@ export function createToolItem(id, name) {
1699
1709
  updateToolGroupHeader(currentToolGroup);
1700
1710
 
1701
1711
  refreshIcons();
1702
- ctx.scrollToBottom();
1712
+ maybeScrollToBottom();
1703
1713
 
1704
1714
  tools[id] = { el: el, name: name, input: null, done: false, groupId: currentToolGroup.id };
1705
1715
  ctx.setActivity("Running " + name + "...");
@@ -1737,7 +1747,7 @@ export function updateToolExecuting(id, name, input) {
1737
1747
  var subtitleText = tool.el.querySelector(".tool-subtitle-text");
1738
1748
  if (subtitleText) subtitleText.textContent = toolActivityText(name, input);
1739
1749
 
1740
- ctx.scrollToBottom();
1750
+ maybeScrollToBottom();
1741
1751
  }
1742
1752
 
1743
1753
  // Shared chrome (filename header + unified/split toggle) for diff renderings.
@@ -2029,7 +2039,7 @@ export function updateToolResult(id, content, isError, images) {
2029
2039
  });
2030
2040
 
2031
2041
  markToolDone(id, isError);
2032
- ctx.scrollToBottom();
2042
+ maybeScrollToBottom();
2033
2043
  }
2034
2044
 
2035
2045
  export function markToolDone(id, isError) {
@@ -2087,7 +2097,7 @@ export function updateSubagentActivity(parentToolId, text) {
2087
2097
  }
2088
2098
 
2089
2099
  ctx.setActivity(text);
2090
- ctx.scrollToBottom();
2100
+ maybeScrollToBottom();
2091
2101
  }
2092
2102
 
2093
2103
  export function addSubagentToolEntry(parentToolId, toolName, toolId, text) {
@@ -2121,7 +2131,7 @@ export function addSubagentToolEntry(parentToolId, toolName, toolId, text) {
2121
2131
  log.scrollTop = log.scrollHeight;
2122
2132
 
2123
2133
  ctx.setActivity(text);
2124
- ctx.scrollToBottom();
2134
+ maybeScrollToBottom();
2125
2135
  }
2126
2136
 
2127
2137
  function fmtTokens(n) {
@@ -2254,7 +2264,7 @@ export function addTurnMeta(cost, duration) {
2254
2264
  if (parts.length) {
2255
2265
  div.textContent = parts.join(" \u00b7 ");
2256
2266
  ctx.addToMessages(div);
2257
- ctx.scrollToBottom();
2267
+ maybeScrollToBottom();
2258
2268
  }
2259
2269
  }
2260
2270
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.36.0",
3
+ "version": "2.36.1-beta.1",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",