diff --git a/.changeset/observe-chat-resize.md b/.changeset/observe-chat-resize.md new file mode 100644 index 00000000000..e3e3dbe7692 --- /dev/null +++ b/.changeset/observe-chat-resize.md @@ -0,0 +1,6 @@ +--- +"@kilocode/kilo-ui": patch +"kilo-code": patch +--- + +Keep chat pinned to the latest output when the visible message area resizes without overriding an intentional scroll position. diff --git a/packages/kilo-ui/src/hooks/create-auto-scroll.tsx b/packages/kilo-ui/src/hooks/create-auto-scroll.tsx index 8b28cfcfdf7..8968defe60f 100644 --- a/packages/kilo-ui/src/hooks/create-auto-scroll.tsx +++ b/packages/kilo-ui/src/hooks/create-auto-scroll.tsx @@ -28,6 +28,7 @@ export function createAutoScroll(options: AutoScrollOptions) { const [store, setStore] = createStore({ contentRef: undefined as HTMLElement | undefined, + scrollRef: undefined as HTMLElement | undefined, userScrolled: false, }) @@ -187,6 +188,20 @@ export function createAutoScroll(options: AutoScrollOptions) { }, ) + createResizeObserver( + () => store.scrollRef, + () => { + const el = scroll + if (!el) return + if (!canScroll(el)) { + if (store.userScrolled) setStore("userScrolled", false) + return + } + if (store.userScrolled || recentlyInteracted()) return + scrollToBottomNow("auto") + }, + ) + createEffect( on(options.working, (working: boolean) => { settling = false @@ -219,6 +234,7 @@ export function createAutoScroll(options: AutoScrollOptions) { } scroll = el + setStore("scrollRef", el) if (!el) return diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 46daa3d76f3..3dc6c7b794b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -5,7 +5,7 @@ * Main chat container that combines all chat components */ -import { type Component, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js" +import { type Component, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { Icon } from "@kilocode/kilo-ui/icon" import { Spinner } from "@kilocode/kilo-ui/spinner" @@ -78,17 +78,6 @@ export const ChatView: Component = (props) => { const questioning = () => isQuestioning(blocked(), familyQuestions().length) const dock = () => !props.readonly || !!permissionRequest() - // When a bottom-dock permission disappears while the session is busy, - // the scroll container grows taller. Dispatch a custom event so MessageList can - // resume auto-scroll. - createEffect( - on(blocked, (isBlocked, wasBlocked) => { - if (wasBlocked && !isBlocked && !idle()) { - window.dispatchEvent(new CustomEvent("resumeAutoScroll")) - } - }), - ) - onMount(() => { if (props.readonly) return const handler = (e: KeyboardEvent) => { diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx index 7b2f1241592..eaf4243e970 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -74,7 +74,7 @@ export const MessageList: Component = (props) => { working: () => session.status() !== "idle", }) - // Resume auto-scroll when a bottom-dock permission/question is dismissed + // Explicit output-producing actions resume auto-scroll before appending. const onResumeAutoScroll = () => autoScroll.resume() window.addEventListener("resumeAutoScroll", onResumeAutoScroll) onCleanup(() => window.removeEventListener("resumeAutoScroll", onResumeAutoScroll))