diff --git a/.changeset/transcript-step-handoff-flicker.md b/.changeset/transcript-step-handoff-flicker.md new file mode 100644 index 000000000000..46507d91027f --- /dev/null +++ b/.changeset/transcript-step-handoff-flicker.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Stop the transcript from twitching down and back up for one frame when the agent starts its next step after running tools 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 9825c9bc0eb0..350e9f112345 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -958,6 +958,29 @@ export const MessageList: Component = (props) => { const indexes = createMemo(() => new Map(keys().map((key, index) => [key, index]))) const fingerprint = createMemo(() => rowFingerprint(keys())) + // A row handed from the direct tail to Virtua (each new step of the same + // turn moves the previous assistant message) mounts at the 260px estimate + // until Virtua's ResizeObserver measures it. The auto-scroll pins to that + // shorter layout, the correction lands in the same ResizeObserver pass, and + // the follow-up pin is deferred to the next frame, so one frame paints with + // the transcript sitting below the bottom. Measure the handed rows in the + // same task and re-pin before anything is painted. + createEffect( + on( + () => ({ sid: session.currentSessionID(), keys: keys() }), + (now, prev) => { + if (!prev || prev.sid !== now.sid) return + if (now.keys.length <= prev.keys.length || now.keys.at(-1) === prev.keys.at(-1)) return + queueMicrotask(() => { + const handle = virtualizer() + if (!handle) return + handle.measure() + autoScroll.scrollToBottom() + }) + }, + ), + ) + const [pending, setPending] = createSignal<{ sid: string; key: string }>() // Scrolls the transcript to a row by key. Virtualized rows jump through diff --git a/patches/virtua@0.49.1.patch b/patches/virtua@0.49.1.patch index d8064d3d010a..b71fefe1b32a 100644 --- a/patches/virtua@0.49.1.patch +++ b/patches/virtua@0.49.1.patch @@ -1,5 +1,5 @@ diff --git a/lib/solid/Virtualizer.d.ts b/lib/solid/Virtualizer.d.ts -index 144dd7f..819aab9 100644 +index 144dd7fba894d440069a8dc0415f4b778a98793d..819aab92c5727d3bca4ef51da08ab05ec1af452f 100644 --- a/lib/solid/Virtualizer.d.ts +++ b/lib/solid/Virtualizer.d.ts @@ -38,6 +38,10 @@ export interface VirtualizerHandle { @@ -13,8 +13,47 @@ index 144dd7f..819aab9 100644 /** * Scroll to the item specified by index. * @param index index of item +diff --git a/lib/solid/index.js b/lib/solid/index.js +index dbf44b0231abaf594c026488dd0261a3fa3fafc7..2fb3544c283a2f241a65be574ad01d0f21db4e3a 100644 +--- a/lib/solid/index.js ++++ b/lib/solid/index.js +@@ -306,7 +306,7 @@ const b = null, {min: z, max: w, abs: x, floor: y} = Math, _ = (e, t, r) => z(r, + }, r); + const c = q(r.data.length, o, void 0, i, !o), l = ((e, t) => { + let r; +- const n = t ? "width" : "height", o = new WeakMap, s = D(t => { ++ const n = t ? "width" : "height", o = new WeakMap, m = new Map, s = D(t => { + const s = []; + for (const {target: i, contentRect: c} of t) if (i.offsetParent) if (i === r) e.H(4, c[n]); else { + const e = o.get(i); +@@ -318,9 +318,15 @@ const b = null, {min: z, max: w, abs: x, floor: y} = Math, _ = (e, t, r) => z(r, + G(e) { + s.q(r = e); + }, +- K: (e, t) => (o.set(e, t), s.q(e), () => { +- o.delete(e), s.N(e); ++ K: (e, t) => (o.set(e, t), m.set(t, e), s.q(e), () => { ++ o.delete(e), m.get(t) === e && m.delete(t), s.N(e); + }), ++ ae() { ++ const t = []; ++ m.forEach((e, r) => { ++ e.offsetParent && t.push([ r, e.getBoundingClientRect()[n] ]); ++ }), t.length && e.H(3, t); ++ }, + p: s.W + }; + })(c, s), $ = ((e, t) => { +@@ -397,6 +403,7 @@ const b = null, {min: z, max: w, abs: x, floor: y} = Math, _ = (e, t, r) => z(r, + findItemIndex: c.$, + getItemOffset: c.I, + getItemSize: c.k, ++ measure: l.ae, + scrollToIndex: $.ne, + scrollTo: $.te, + scrollBy: $.re diff --git a/lib/solid/index.jsx b/lib/solid/index.jsx -index 029201a..3949cd4 100644 +index 029201a2c88fe71645242b3f6f11493a7f91fa86..3949cd43438aae3edc5f8c9cdd6c75fb7c091e83 100644 --- a/lib/solid/index.jsx +++ b/lib/solid/index.jsx @@ -1085,6 +1085,7 @@ const createResizer = (store, isHorizontal) => {