fix(frontend): reset live log follow state on resume

This commit is contained in:
User
2026-07-15 12:04:05 +02:00
parent 279b79516c
commit 00c1af5a5a
2 changed files with 43 additions and 1 deletions
+38
View File
@@ -83,3 +83,41 @@ test("preserves manual live-log scroll position away from the bottom", () => {
expect(log.scrollTop).toBe(100);
});
test("resets bottom-follow when the live log resumes after becoming inactive", () => {
useSessionStore.setState({ transcript: [{ role: "assistant", text: "First" }] });
const { rerender } = render(<CentralStatus working />);
const log = screen.getByRole("log", { name: "Live model activity" });
setScrollGeometry(log, { scrollHeight: 400, clientHeight: 100, scrollTop: 100 });
fireEvent.scroll(log);
rerender(<CentralStatus working={false} />);
expect(screen.queryByRole("log", { name: "Live model activity" })).not.toBeInTheDocument();
const scrollProperties = ["scrollHeight", "clientHeight", "scrollTop"] as const;
const originalDescriptors = scrollProperties.map((property) =>
Object.getOwnPropertyDescriptor(HTMLElement.prototype, property),
);
Object.defineProperties(HTMLElement.prototype, {
scrollHeight: { configurable: true, value: 400 },
clientHeight: { configurable: true, value: 100 },
scrollTop: { configurable: true, writable: true, value: 0 },
});
try {
rerender(<CentralStatus working />);
const resumedLog = screen.getByRole("log", { name: "Live model activity" });
expect(resumedLog.scrollTop).toBe(400);
Object.defineProperty(resumedLog, "scrollHeight", { configurable: true, value: 500 });
act(() => useSessionStore.getState().applyEvent({ type: "text_delta", text: " second" }));
expect(resumedLog.scrollTop).toBe(500);
} finally {
scrollProperties.forEach((property, index) => {
const descriptor = originalDescriptors[index];
if (descriptor) Object.defineProperty(HTMLElement.prototype, property, descriptor);
else Reflect.deleteProperty(HTMLElement.prototype, property);
});
}
});
+5 -1
View File
@@ -21,9 +21,13 @@ export function CentralStatus({ working }: { working: boolean }) {
const followRef = useRef(true);
useLayoutEffect(() => {
if (!working) {
followRef.current = true;
return;
}
const viewport = scrollRef.current;
if (viewport && followRef.current) viewport.scrollTop = viewport.scrollHeight;
}, [rowCount, tail]);
}, [working, rowCount, tail]);
if (!working || rows.length === 0) return null;