feat(KB-091): add auto-scroll to agent log viewer

- Add useRef and useEffect hooks to track container scroll position
- Auto-scroll to top when new entries arrive while user is near top (within 50px)
- Preserve model info header display with executor/validator models
- Add tests for auto-scroll behavior (scrolls when at top, preserves position when scrolled)
- All 18 AgentLogViewer tests pass
This commit is contained in:
gsxdsm
2026-03-31 11:52:02 -07:00
parent 89bd17f06b
commit ca31314be0
2 changed files with 58 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
import { describe, it, expect } from "vitest";
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { AgentLogViewer } from "../AgentLogViewer";
import type { AgentLogEntry } from "@kb/core";
@@ -330,4 +330,34 @@ describe("AgentLogViewer", () => {
expect(header!.textContent).toContain("Using default");
});
});
describe("auto-scroll behavior", () => {
it("scrolls to top when new entries arrive and user is near the top", () => {
const { rerender, container } = render(<AgentLogViewer entries={[makeEntry({ text: "first" })]} loading={false} />);
const viewer = container.querySelector("[data-testid='agent-log-viewer']") as HTMLDivElement;
// Simulate user being at the top
viewer.scrollTop = 0;
// Add a new entry
rerender(<AgentLogViewer entries={[makeEntry({ text: "second" }), makeEntry({ text: "first" })]} loading={false} />);
// Should have scrolled to top (newest first)
expect(viewer.scrollTop).toBe(0);
});
it("does not auto-scroll when user has scrolled down", () => {
const { rerender, container } = render(<AgentLogViewer entries={[makeEntry({ text: "first" })]} loading={false} />);
const viewer = container.querySelector("[data-testid='agent-log-viewer']") as HTMLDivElement;
// Simulate user scrolling down past the threshold
Object.defineProperty(viewer, 'scrollTop', { value: 100, writable: true });
// Add a new entry
rerender(<AgentLogViewer entries={[makeEntry({ text: "second" }), makeEntry({ text: "first" })]} loading={false} />);
// Should not have scrolled (scrollTop should remain 100)
expect(viewer.scrollTop).toBe(100);
});
});
});