fix(ui): scroll chat to bottom on initial load

The chat view was starting at the top showing oldest messages instead of
scrolling to the bottom to show the latest messages (like WhatsApp).

Root causes:
1. scheduleChatScroll() was called without force flag in refreshActiveTab()
2. The scroll was targeting .chat-thread element which has overflow:visible
   and doesn't actually scroll - the window scrolls instead

Fixes:
- Pass force flag (!chatHasAutoScrolled) when loading chat tab
- Wait for Lit updateComplete before scrolling to ensure DOM is ready
- Scroll the window instead of the .chat-thread container
- Use behavior: 'instant' for immediate scroll without animation
This commit is contained in:
kiranjd
2026-01-06 11:23:27 +05:30
parent 79e4354e5c
commit 511632f47c

View File

@@ -437,25 +437,35 @@ export class ClawdbotApp extends LitElement {
clearTimeout(this.chatScrollTimeout); clearTimeout(this.chatScrollTimeout);
this.chatScrollTimeout = null; this.chatScrollTimeout = null;
} }
this.chatScrollFrame = requestAnimationFrame(() => { // Wait for Lit render to complete, then scroll
this.chatScrollFrame = null; void this.updateComplete.then(() => {
const container = this.querySelector(".chat-thread") as HTMLElement | null; this.chatScrollFrame = requestAnimationFrame(() => {
if (!container) return; this.chatScrollFrame = null;
const distanceFromBottom = if (force) {
container.scrollHeight - container.scrollTop - container.clientHeight; // Force scroll window to bottom unconditionally
const shouldStick = force || distanceFromBottom < 140; this.chatHasAutoScrolled = true;
if (!shouldStick) return; window.scrollTo({ top: document.body.scrollHeight, behavior: "instant" });
if (force) this.chatHasAutoScrolled = true; // Retry after images/content load
container.scrollTop = container.scrollHeight; this.chatScrollTimeout = window.setTimeout(() => {
this.chatScrollTimeout = window.setTimeout(() => { this.chatScrollTimeout = null;
this.chatScrollTimeout = null; window.scrollTo({ top: document.body.scrollHeight, behavior: "instant" });
const latest = this.querySelector(".chat-thread") as HTMLElement | null; }, 150);
if (!latest) return; return;
const latestDistanceFromBottom = }
latest.scrollHeight - latest.scrollTop - latest.clientHeight; // Stick to bottom if already near bottom
if (!force && latestDistanceFromBottom >= 180) return; const distanceFromBottom =
latest.scrollTop = latest.scrollHeight; document.body.scrollHeight - window.scrollY - window.innerHeight;
}, 120); const shouldStick = distanceFromBottom < 200;
if (!shouldStick) return;
window.scrollTo({ top: document.body.scrollHeight, behavior: "instant" });
this.chatScrollTimeout = window.setTimeout(() => {
this.chatScrollTimeout = null;
const latestDistanceFromBottom =
document.body.scrollHeight - window.scrollY - window.innerHeight;
if (latestDistanceFromBottom >= 250) return;
window.scrollTo({ top: document.body.scrollHeight, behavior: "instant" });
}, 120);
});
}); });
} }
@@ -689,7 +699,7 @@ export class ClawdbotApp extends LitElement {
if (this.tab === "nodes") await loadNodes(this); if (this.tab === "nodes") await loadNodes(this);
if (this.tab === "chat") { if (this.tab === "chat") {
await Promise.all([loadChatHistory(this), loadSessions(this)]); await Promise.all([loadChatHistory(this), loadSessions(this)]);
this.scheduleChatScroll(); this.scheduleChatScroll(!this.chatHasAutoScrolled);
} }
if (this.tab === "config") { if (this.tab === "config") {
await loadConfigSchema(this); await loadConfigSchema(this);