From bc475f0172d15a896596d35969441ff57e1356f2 Mon Sep 17 00:00:00 2001 From: Tyler Yust Date: Sat, 7 Feb 2026 20:21:27 -0800 Subject: [PATCH] fix(ui): smooth chat refresh scroll and suppress new-messages badge flash --- CHANGELOG.md | 1 + ui/src/ui/app-chat.ts | 6 ++++-- ui/src/ui/app-lifecycle.ts | 4 ++++ ui/src/ui/app-render.helpers.ts | 20 +++++++++++++++++--- ui/src/ui/app-render.ts | 2 +- ui/src/ui/app-scroll.ts | 14 ++++++++++++-- ui/src/ui/app-view-state.ts | 3 ++- ui/src/ui/app.ts | 4 +++- 8 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e655f64b..abd96e052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Web UI: make chat refresh smoothly scroll to the latest messages and suppress new-messages badge flash during manual refresh. - Cron: route text-only isolated agent announces through the shared subagent announce flow; add exponential backoff for repeated errors; preserve future `nextRunAtMs` on restart; include current-boundary schedule matches; prevent stale threadId reuse across targets; and add per-job execution timeout. (#11641) Thanks @tyler6204. - Subagents: stabilize announce timing, preserve compaction metrics across retries, clamp overflow-prone long timeouts, and cap impossible context usage token totals. (#11551) Thanks @tyler6204. - Agents: recover from context overflow caused by oversized tool results (pre-emptive capping + fallback truncation). (#11579) Thanks @tyler6204. diff --git a/ui/src/ui/app-chat.ts b/ui/src/ui/app-chat.ts index b8e09def1..c648a89d1 100644 --- a/ui/src/ui/app-chat.ts +++ b/ui/src/ui/app-chat.ts @@ -202,7 +202,7 @@ export async function handleSendChat( }); } -export async function refreshChat(host: ChatHost) { +export async function refreshChat(host: ChatHost, opts?: { scheduleScroll?: boolean }) { await Promise.all([ loadChatHistory(host as unknown as OpenClawApp), loadSessions(host as unknown as OpenClawApp, { @@ -210,7 +210,9 @@ export async function refreshChat(host: ChatHost) { }), refreshChatAvatar(host), ]); - scheduleChatScroll(host as unknown as Parameters[0]); + if (opts?.scheduleScroll !== false) { + scheduleChatScroll(host as unknown as Parameters[0]); + } } export const flushChatQueueForEvent = flushChatQueue; diff --git a/ui/src/ui/app-lifecycle.ts b/ui/src/ui/app-lifecycle.ts index 32af804fb..9a9826103 100644 --- a/ui/src/ui/app-lifecycle.ts +++ b/ui/src/ui/app-lifecycle.ts @@ -22,6 +22,7 @@ type LifecycleHost = { basePath: string; tab: Tab; chatHasAutoScrolled: boolean; + chatManualRefreshInFlight: boolean; chatLoading: boolean; chatMessages: unknown[]; chatToolMessages: unknown[]; @@ -65,6 +66,9 @@ export function handleDisconnected(host: LifecycleHost) { } export function handleUpdated(host: LifecycleHost, changed: Map) { + if (host.tab === "chat" && host.chatManualRefreshInFlight) { + return; + } if ( host.tab === "chat" && (changed.has("chatMessages") || diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index c12258599..eaf6eabdc 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -126,9 +126,23 @@ export function renderChatControls(state: AppViewState) {