mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-04 23:52:53 +00:00
fix(ui): scroll pending sends into view
Scroll the chat thread as soon as a submitted pending send is enqueued, so delayed `chat.send` ACKs no longer leave the user's just-sent message below the viewport. Verification: - focused UI Vitest suite: 86 tests passed - oxlint, core tsgo, core-test tsgo, diff check - Testbox changed gate: tbx_01kt0wspy1ks5wpb6kp5gr0512 - branch autoreview clean
This commit is contained in:
@@ -417,6 +417,7 @@ function enqueuePendingSendMessage(
|
||||
};
|
||||
host.chatQueue = [...host.chatQueue, pending];
|
||||
recordChatSendTiming(host, pending, "pending-visible", submittedAtMs);
|
||||
scheduleChatScroll(host as unknown as Parameters<typeof scheduleChatScroll>[0], true);
|
||||
return pending;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { chromium, type Browser } from "playwright";
|
||||
import { chromium, type Browser, type Page } from "playwright";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import {
|
||||
canRunPlaywrightChromium,
|
||||
@@ -49,6 +49,13 @@ async function waitForRequests(
|
||||
throw new Error(`Timed out waiting for ${count} ${method} requests`);
|
||||
}
|
||||
|
||||
async function chatThreadDistanceFromBottom(page: Page): Promise<number> {
|
||||
return page.locator(".chat-thread").evaluate((element) => {
|
||||
const thread = element as HTMLElement;
|
||||
return Math.round(thread.scrollHeight - thread.scrollTop - thread.clientHeight);
|
||||
});
|
||||
}
|
||||
|
||||
function chatSessionListResponse() {
|
||||
return {
|
||||
count: 2,
|
||||
@@ -322,6 +329,63 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("scrolls a delayed pending send into view before the ACK resolves", async () => {
|
||||
const context = await browser.newContext({
|
||||
locale: "en-US",
|
||||
serviceWorkers: "block",
|
||||
viewport: { height: 900, width: 1280 },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const baseTs = Date.now() - 100_000;
|
||||
const historyMessages = Array.from({ length: 50 }, (_, index) => ({
|
||||
content: [
|
||||
{
|
||||
text: `History message ${index}\n${"extra transcript line\n".repeat(4)}`,
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
role: index % 2 === 0 ? "assistant" : "user",
|
||||
timestamp: baseTs + index,
|
||||
}));
|
||||
const gateway = await installMockGateway(page, { historyMessages });
|
||||
|
||||
try {
|
||||
await page.goto(`${server.baseUrl}chat`);
|
||||
await page.getByText("History message 49").waitFor({ timeout: 10_000 });
|
||||
await expect
|
||||
.poll(() => chatThreadDistanceFromBottom(page), { timeout: 10_000 })
|
||||
.toBeLessThanOrEqual(4);
|
||||
|
||||
await page.locator(".chat-thread").evaluate((element) => {
|
||||
(element as HTMLElement).scrollTop = 0;
|
||||
});
|
||||
await expect
|
||||
.poll(() => chatThreadDistanceFromBottom(page), { timeout: 10_000 })
|
||||
.toBeGreaterThan(200);
|
||||
|
||||
await gateway.deferNext("chat.send");
|
||||
|
||||
const prompt = `pending send should scroll before ack\n${"visible now\n".repeat(6)}`;
|
||||
await page.locator(".agent-chat__composer-combobox textarea").fill(prompt);
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
const sendRequest = await gateway.waitForRequest("chat.send");
|
||||
const params = requireRecord(sendRequest.params);
|
||||
const runId = requireString(params.idempotencyKey, "chat send idempotency key");
|
||||
|
||||
await page.locator(".chat-thread").getByText("pending send should scroll").waitFor({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await expect
|
||||
.poll(() => chatThreadDistanceFromBottom(page), { timeout: 10_000 })
|
||||
.toBeLessThanOrEqual(4);
|
||||
|
||||
await gateway.resolveDeferred("chat.send", { runId, status: "started" });
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps rejected pre-ACK sends visible and restores the draft", async () => {
|
||||
const context = await browser.newContext({
|
||||
locale: "en-US",
|
||||
|
||||
Reference in New Issue
Block a user