* fix(app): align turn footer actions Keep running-turn actions in the same position as completed-turn actions and use the shared small icon size for both copy and fork controls. * feat(app): improve sidebar workspace identity Make dense sidebar workspace rows easier to scan and let each device control how connected hosts are identified. * fix(app): keep host badges readable Use a compact display menu and keep identity color on the host icon so translated controls fit and badge text retains standard contrast. * fix(app): announce selected sidebar badge mode * fix(app): retain icon-only host names for screen readers * fix(app): name hoisted workspace rows * fix(app): retain host identity in workspace labels * fix(app): retain workspace status in row labels * fix(app): defer host appearance until identity resolves * fix(app): serialize host appearance writes * fix(app): serialize host registry writes * fix(app): bound sidebar host badge labels * test(app): match workspace status labels * fix(app): consolidate sidebar identity behavior * test(app): assert status grouping behavior * fix(app): keep sidebar row actions accessible * fix(app): stabilize optimistic message layout
113 lines
3.5 KiB
TypeScript
113 lines
3.5 KiB
TypeScript
import { expect, type Page } from "@playwright/test";
|
|
import { gotoAppShell } from "../support/helpers/app";
|
|
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
|
|
import { waitForSidebarHydration } from "../support/helpers/workspace-ui";
|
|
|
|
export interface StatusProject {
|
|
seed: SeededWorkspace;
|
|
needsInputWorkspaceId: string;
|
|
}
|
|
|
|
export async function seedStatusProject(): Promise<StatusProject> {
|
|
const seed = await seedWorkspace({
|
|
repoPrefix: "project-status-badge-",
|
|
title: "Working workspace",
|
|
});
|
|
const created = await seed.client.createWorkspace({
|
|
source: { kind: "directory", path: seed.repoPath, projectId: seed.projectId },
|
|
title: "Needs input workspace",
|
|
});
|
|
if (!created.workspace) {
|
|
await seed.cleanup();
|
|
throw new Error(created.error ?? "Failed to create the needs-input workspace");
|
|
}
|
|
return { seed, needsInputWorkspaceId: created.workspace.id };
|
|
}
|
|
|
|
export async function openAndCollapseStatusProject(
|
|
page: Page,
|
|
project: StatusProject,
|
|
): Promise<void> {
|
|
await gotoAppShell(page);
|
|
await waitForSidebarHydration(page);
|
|
await projectRow(page, project).click();
|
|
await page.mouse.move(1200, 850);
|
|
await expect(workspaceRow(page, "Working workspace")).toHaveCount(0);
|
|
}
|
|
|
|
export async function expandStatusProject(page: Page, project: StatusProject): Promise<void> {
|
|
await projectRow(page, project).click();
|
|
await expect(workspaceRow(page, "Working workspace")).toBeVisible();
|
|
}
|
|
|
|
export async function startWorkingWorkspace(project: StatusProject): Promise<void> {
|
|
await project.seed.client.createAgent({
|
|
provider: "mock",
|
|
cwd: project.seed.repoPath,
|
|
workspaceId: project.seed.workspaceId,
|
|
title: "Working agent",
|
|
modeId: "load-test",
|
|
model: "thirty-minute-stream",
|
|
initialPrompt: "keep streaming for the test",
|
|
});
|
|
}
|
|
|
|
export async function startNeedsInputWorkspace(project: StatusProject): Promise<void> {
|
|
await project.seed.client.createAgent({
|
|
provider: "mock",
|
|
cwd: project.seed.repoPath,
|
|
workspaceId: project.needsInputWorkspaceId,
|
|
title: "Needs input agent",
|
|
modeId: "load-test",
|
|
model: "thirty-minute-stream",
|
|
initialPrompt: "emit a synthetic plan approval",
|
|
});
|
|
}
|
|
|
|
export async function expectCollapsedProjectStatus(
|
|
page: Page,
|
|
project: StatusProject,
|
|
status: "Working" | "Needs input",
|
|
): Promise<void> {
|
|
await expect(projectRow(page, project).getByRole("status", { name: status })).toBeVisible({
|
|
timeout: 60_000,
|
|
});
|
|
}
|
|
|
|
export async function expectProjectStatusHidden(page: Page, project: StatusProject): Promise<void> {
|
|
await expect(projectRow(page, project).getByRole("status")).toHaveCount(0, {
|
|
timeout: 60_000,
|
|
});
|
|
}
|
|
|
|
export async function expectWorkspaceStatus(
|
|
page: Page,
|
|
workspaceName: string,
|
|
status: "running" | "needs_input",
|
|
): Promise<void> {
|
|
await expect(
|
|
workspaceRow(page, workspaceName).getByTestId(`workspace-status-indicator-${status}`),
|
|
).toBeVisible({ timeout: 60_000 });
|
|
}
|
|
|
|
function projectGroup(page: Page, project: StatusProject) {
|
|
return page.getByRole("group", { name: project.seed.projectDisplayName, exact: true });
|
|
}
|
|
|
|
function projectRow(page: Page, project: StatusProject) {
|
|
return projectGroup(page, project)
|
|
.getByRole("button")
|
|
.filter({ hasText: project.seed.projectDisplayName })
|
|
.first();
|
|
}
|
|
|
|
function workspaceRow(page: Page, workspaceName: string) {
|
|
return page.getByRole("button", {
|
|
name: new RegExp(`^${escapeRegExp(workspaceName)}(?:,|$)`),
|
|
});
|
|
}
|
|
|
|
function escapeRegExp(value: string): string {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|