feat(webui): add prompt rail navigation

This commit is contained in:
Xubin Ren
2026-06-02 16:18:57 +08:00
parent e8d4aff5be
commit 1af2bc513f
8 changed files with 312 additions and 7 deletions
@@ -9,7 +9,7 @@ describe("MarkdownTextRenderer", () => {
const link = screen.getByRole("link", { name: "local server" });
expect(link).toHaveAttribute("href", "http://127.0.0.1:7891/");
expect(link).toHaveClass("text-blue-600", "dark:text-blue-300");
expect(link).toHaveClass("text-blue-500", "dark:text-blue-300");
});
it("does not wrap complete fenced code blocks in an extra pre", () => {
+45
View File
@@ -170,6 +170,51 @@ describe("ThreadViewport", () => {
expect(screen.getByText("message 299")).toBeInTheDocument();
});
it("renders a prompt rail that jumps to user messages", async () => {
const promptMessages = makeLongMessages(5);
const { container } = render(
<ThreadViewport
messages={promptMessages}
isStreaming={false}
composer={<div />}
/>,
);
const scroller = container.firstElementChild?.firstElementChild as HTMLElement;
const scrollTo = vi.fn();
Object.defineProperties(scroller, {
scrollHeight: { configurable: true, value: 1800 },
clientHeight: { configurable: true, value: 600 },
scrollTop: { configurable: true, value: 0 },
scrollTo: { configurable: true, value: scrollTo },
});
const promptEls = Array.from(
container.querySelectorAll<HTMLElement>("[data-user-prompt-id]"),
);
expect(promptEls).toHaveLength(5);
promptEls.forEach((el, index) => {
Object.defineProperty(el, "offsetTop", {
configurable: true,
value: index * 360,
});
});
await act(async () => {
window.dispatchEvent(new Event("resize"));
await new Promise<void>((resolve) => window.requestAnimationFrame(() => resolve()));
});
expect(screen.getByLabelText("User prompt navigation")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Jump to prompt: message 3" }));
expect(scrollTo).toHaveBeenCalledWith({
top: 1064,
behavior: "smooth",
});
});
it("expands the window start to avoid cutting an agent activity cluster", () => {
const clustered = makeLongMessages(200);
clustered.splice(