fix(ui): center running-task preview over link (#114069)

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
ClawSweeper
2026-07-26 01:39:30 -07:00
committed by GitHub
parent 27ede408df
commit 203ef71ca3
3 changed files with 45 additions and 24 deletions
+22 -1
View File
@@ -273,6 +273,27 @@ describeControlUiE2e("Control UI chat background-tasks rail mocked Gateway E2E",
expect(await page.locator(".chat-tasks-status__link").textContent()).toContain(
"1 running task",
);
const statusLink = page.locator(".chat-tasks-status__link");
await statusLink.hover();
const previewBody = page.locator(
"openclaw-tooltip.chat-tasks-status__preview wa-tooltip[open] .body",
);
await previewBody.waitFor({ state: "visible" });
const linkBox = await statusLink.boundingBox();
const previewBox = await previewBody.boundingBox();
expect(linkBox).not.toBeNull();
expect(previewBox).not.toBeNull();
if (!linkBox || !previewBox) {
throw new Error("expected running-task link and preview geometry");
}
const linkCenter = linkBox.x + linkBox.width / 2;
const previewCenter = previewBox.x + previewBox.width / 2;
expect(Math.abs(previewCenter - linkCenter)).toBeLessThanOrEqual(2);
expect(previewBox.y + previewBox.height).toBeLessThanOrEqual(linkBox.y);
await page.screenshot({
path: path.join(artifactDir, "05-running-task-popover-centered.png"),
fullPage: true,
});
await page.getByRole("button", { name: "Show background tasks" }).click();
const row = page.locator('[data-task-id="task-exec"]');
@@ -280,7 +301,7 @@ describeControlUiE2e("Control UI chat background-tasks rail mocked Gateway E2E",
expect(await row.textContent()).toContain("CLI command");
expect(await row.textContent()).toContain("Command running");
await page.screenshot({
path: path.join(artifactDir, "05-one-background-exec.png"),
path: path.join(artifactDir, "06-one-background-exec.png"),
fullPage: true,
});
} finally {
@@ -110,25 +110,25 @@ export function renderBackgroundTasksStatusRow(
backgroundTasks.onToggleCollapsed();
}
};
// The preview tooltip anchors the whole row (not the link button), so the
// ticking preview content stays outside the polite live region.
// Keep the live announcement separate from the tooltip: rich preview
// content must not enter the polite region, while the popup must anchor to
// the link itself or its center drifts with the claw and elapsed time.
return html`
<openclaw-tooltip class="chat-tasks-status__preview">
<div class="chat-tasks-status" id=${backgroundTasks.statusRowId} role="status">
<span class="chat-tasks-status__claw" aria-hidden="true">${icons.claw}</span>
${status.startedMs !== null
? html`
<!-- Ticking time stays out of the polite live region: without
aria-hidden, screen readers would re-announce every second. -->
<span class="chat-tasks-status__time" aria-hidden="true">
<openclaw-elapsed-time .startMs=${status.startedMs}></openclaw-elapsed-time>
</span>
<span class="chat-tasks-status__sep" aria-hidden="true">·</span>
`
: nothing}
<div class="chat-tasks-status" id=${backgroundTasks.statusRowId}>
<span class="chat-tasks-status__claw" aria-hidden="true">${icons.claw}</span>
${status.startedMs !== null
? html`
<span class="chat-tasks-status__time" aria-hidden="true">
<openclaw-elapsed-time .startMs=${status.startedMs}></openclaw-elapsed-time>
</span>
<span class="chat-tasks-status__sep" aria-hidden="true">·</span>
`
: nothing}
<span class="agent-chat__sr-only" role="status">${label}</span>
<openclaw-tooltip class="chat-tasks-status__preview">
<button class="chat-tasks-status__link" type="button" @click=${openRail}>${label}</button>
</div>
${renderStatusPreview(backgroundTasks)}
</openclaw-tooltip>
${renderStatusPreview(backgroundTasks)}
</openclaw-tooltip>
</div>
`;
}
@@ -761,10 +761,9 @@ describe("running-tasks status row", () => {
const row = container.querySelector(".chat-tasks-status");
expect(row).not.toBeNull();
expect(row?.querySelector("openclaw-elapsed-time")).not.toBeNull();
// The ticking timer must stay outside the polite live region.
expect(row?.querySelector(".chat-tasks-status__time")?.getAttribute("aria-hidden")).toBe(
"true",
);
const liveStatus = row?.querySelector('[role="status"]');
expect(liveStatus?.textContent?.trim()).toBe("1 running task");
expect(liveStatus?.querySelector("openclaw-elapsed-time")).toBeNull();
const link = row?.querySelector<HTMLButtonElement>(".chat-tasks-status__link");
expect(link?.textContent?.trim()).toBe("1 running task");
link?.click();
@@ -812,7 +811,8 @@ describe("running-tasks status row", () => {
);
const preview = container.querySelector("openclaw-tooltip.chat-tasks-status__preview");
expect(preview?.querySelector(".chat-tasks-status")?.id).toBe("chat-tasks-status-test");
expect(preview?.firstElementChild?.classList.contains("chat-tasks-status__link")).toBe(true);
expect(container.querySelector(".chat-tasks-status")?.id).toBe("chat-tasks-status-test");
expect(preview?.querySelector('.chat-tasks-preview[slot="content"]')).not.toBeNull();
const titles = [...container.querySelectorAll(".chat-tasks-preview__title")].map((el) =>
el.textContent?.trim(),